linux/drivers/extcon/extcon-axp288.c
<<
>>
Prefs
   1/*
   2 * extcon-axp288.c - X-Power AXP288 PMIC extcon cable detection driver
   3 *
   4 * Copyright (C) 2015 Intel Corporation
   5 * Author: Ramakrishna Pallala <ramakrishna.pallala@intel.com>
   6 *
   7 * This program is free software; you can redistribute it and/or modify
   8 * it under the terms of the GNU General Public License version 2 as
   9 * published by the Free Software Foundation.
  10 *
  11 * This program is distributed in the hope that it will be useful,
  12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
  13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  14 * GNU General Public License for more details.
  15 */
  16
  17#include <linux/module.h>
  18#include <linux/kernel.h>
  19#include <linux/io.h>
  20#include <linux/slab.h>
  21#include <linux/interrupt.h>
  22#include <linux/platform_device.h>
  23#include <linux/property.h>
  24#include <linux/usb/phy.h>
  25#include <linux/notifier.h>
  26#include <linux/extcon.h>
  27#include <linux/regmap.h>
  28#include <linux/gpio.h>
  29#include <linux/gpio/consumer.h>
  30#include <linux/mfd/axp20x.h>
  31
  32/* Power source status register */
  33#define PS_STAT_VBUS_TRIGGER            BIT(0)
  34#define PS_STAT_BAT_CHRG_DIR            BIT(2)
  35#define PS_STAT_VBUS_ABOVE_VHOLD        BIT(3)
  36#define PS_STAT_VBUS_VALID              BIT(4)
  37#define PS_STAT_VBUS_PRESENT            BIT(5)
  38
  39/* BC module global register */
  40#define BC_GLOBAL_RUN                   BIT(0)
  41#define BC_GLOBAL_DET_STAT              BIT(2)
  42#define BC_GLOBAL_DBP_TOUT              BIT(3)
  43#define BC_GLOBAL_VLGC_COM_SEL          BIT(4)
  44#define BC_GLOBAL_DCD_TOUT_MASK         (BIT(6)|BIT(5))
  45#define BC_GLOBAL_DCD_TOUT_300MS        0
  46#define BC_GLOBAL_DCD_TOUT_100MS        1
  47#define BC_GLOBAL_DCD_TOUT_500MS        2
  48#define BC_GLOBAL_DCD_TOUT_900MS        3
  49#define BC_GLOBAL_DCD_DET_SEL           BIT(7)
  50
  51/* BC module vbus control and status register */
  52#define VBUS_CNTL_DPDM_PD_EN            BIT(4)
  53#define VBUS_CNTL_DPDM_FD_EN            BIT(5)
  54#define VBUS_CNTL_FIRST_PO_STAT         BIT(6)
  55
  56/* BC USB status register */
  57#define USB_STAT_BUS_STAT_MASK          (BIT(3)|BIT(2)|BIT(1)|BIT(0))
  58#define USB_STAT_BUS_STAT_SHIFT         0
  59#define USB_STAT_BUS_STAT_ATHD          0
  60#define USB_STAT_BUS_STAT_CONN          1
  61#define USB_STAT_BUS_STAT_SUSP          2
  62#define USB_STAT_BUS_STAT_CONF          3
  63#define USB_STAT_USB_SS_MODE            BIT(4)
  64#define USB_STAT_DEAD_BAT_DET           BIT(6)
  65#define USB_STAT_DBP_UNCFG              BIT(7)
  66
  67/* BC detect status register */
  68#define DET_STAT_MASK                   (BIT(7)|BIT(6)|BIT(5))
  69#define DET_STAT_SHIFT                  5
  70#define DET_STAT_SDP                    1
  71#define DET_STAT_CDP                    2
  72#define DET_STAT_DCP                    3
  73
  74/* IRQ enable-1 register */
  75#define PWRSRC_IRQ_CFG_MASK             (BIT(4)|BIT(3)|BIT(2))
  76
  77/* IRQ enable-6 register */
  78#define BC12_IRQ_CFG_MASK               BIT(1)
  79
  80enum axp288_extcon_reg {
  81        AXP288_PS_STAT_REG              = 0x00,
  82        AXP288_PS_BOOT_REASON_REG       = 0x02,
  83        AXP288_BC_GLOBAL_REG            = 0x2c,
  84        AXP288_BC_VBUS_CNTL_REG         = 0x2d,
  85        AXP288_BC_USB_STAT_REG          = 0x2e,
  86        AXP288_BC_DET_STAT_REG          = 0x2f,
  87        AXP288_PWRSRC_IRQ_CFG_REG       = 0x40,
  88        AXP288_BC12_IRQ_CFG_REG         = 0x45,
  89};
  90
  91enum axp288_mux_select {
  92        EXTCON_GPIO_MUX_SEL_PMIC = 0,
  93        EXTCON_GPIO_MUX_SEL_SOC,
  94};
  95
  96enum axp288_extcon_irq {
  97        VBUS_FALLING_IRQ = 0,
  98        VBUS_RISING_IRQ,
  99        MV_CHNG_IRQ,
 100        BC_USB_CHNG_IRQ,
 101        EXTCON_IRQ_END,
 102};
 103
 104static const unsigned int axp288_extcon_cables[] = {
 105        EXTCON_CHG_USB_SDP,
 106        EXTCON_CHG_USB_CDP,
 107        EXTCON_CHG_USB_DCP,
 108        EXTCON_NONE,
 109};
 110
 111struct axp288_extcon_info {
 112        struct device *dev;
 113        struct regmap *regmap;
 114        struct regmap_irq_chip_data *regmap_irqc;
 115        struct axp288_extcon_pdata *pdata;
 116        int irq[EXTCON_IRQ_END];
 117        struct extcon_dev *edev;
 118        struct notifier_block extcon_nb;
 119        struct usb_phy *otg;
 120};
 121
 122/* Power up/down reason string array */
 123static char *axp288_pwr_up_down_info[] = {
 124        "Last wake caused by user pressing the power button",
 125        "Last wake caused by a charger insertion",
 126        "Last wake caused by a battery insertion",
 127        "Last wake caused by SOC initiated global reset",
 128        "Last wake caused by cold reset",
 129        "Last shutdown caused by PMIC UVLO threshold",
 130        "Last shutdown caused by SOC initiated cold off",
 131        "Last shutdown caused by user pressing the power button",
 132        NULL,
 133};
 134
 135/*
 136 * Decode and log the given "reset source indicator" (rsi)
 137 * register and then clear it.
 138 */
 139static void axp288_extcon_log_rsi(struct axp288_extcon_info *info)
 140{
 141        char **rsi;
 142        unsigned int val, i, clear_mask = 0;
 143        int ret;
 144
 145        ret = regmap_read(info->regmap, AXP288_PS_BOOT_REASON_REG, &val);
 146        for (i = 0, rsi = axp288_pwr_up_down_info; *rsi; rsi++, i++) {
 147                if (val & BIT(i)) {
 148                        dev_dbg(info->dev, "%s\n", *rsi);
 149                        clear_mask |= BIT(i);
 150                }
 151        }
 152
 153        /* Clear the register value for next reboot (write 1 to clear bit) */
 154        regmap_write(info->regmap, AXP288_PS_BOOT_REASON_REG, clear_mask);
 155}
 156
 157static int axp288_handle_chrg_det_event(struct axp288_extcon_info *info)
 158{
 159        static bool notify_otg, notify_charger;
 160        static unsigned int cable;
 161        int ret, stat, cfg, pwr_stat;
 162        u8 chrg_type;
 163        bool vbus_attach = false;
 164
 165        ret = regmap_read(info->regmap, AXP288_PS_STAT_REG, &pwr_stat);
 166        if (ret < 0) {
 167                dev_err(info->dev, "failed to read vbus status\n");
 168                return ret;
 169        }
 170
 171        vbus_attach = (pwr_stat & PS_STAT_VBUS_PRESENT);
 172        if (!vbus_attach)
 173                goto notify_otg;
 174
 175        /* Check charger detection completion status */
 176        ret = regmap_read(info->regmap, AXP288_BC_GLOBAL_REG, &cfg);
 177        if (ret < 0)
 178                goto dev_det_ret;
 179        if (cfg & BC_GLOBAL_DET_STAT) {
 180                dev_dbg(info->dev, "can't complete the charger detection\n");
 181                goto dev_det_ret;
 182        }
 183
 184        ret = regmap_read(info->regmap, AXP288_BC_DET_STAT_REG, &stat);
 185        if (ret < 0)
 186                goto dev_det_ret;
 187
 188        chrg_type = (stat & DET_STAT_MASK) >> DET_STAT_SHIFT;
 189
 190        switch (chrg_type) {
 191        case DET_STAT_SDP:
 192                dev_dbg(info->dev, "sdp cable is connected\n");
 193                notify_otg = true;
 194                notify_charger = true;
 195                cable = EXTCON_CHG_USB_SDP;
 196                break;
 197        case DET_STAT_CDP:
 198                dev_dbg(info->dev, "cdp cable is connected\n");
 199                notify_otg = true;
 200                notify_charger = true;
 201                cable = EXTCON_CHG_USB_CDP;
 202                break;
 203        case DET_STAT_DCP:
 204                dev_dbg(info->dev, "dcp cable is connected\n");
 205                notify_charger = true;
 206                cable = EXTCON_CHG_USB_DCP;
 207                break;
 208        default:
 209                dev_warn(info->dev,
 210                        "disconnect or unknown or ID event\n");
 211        }
 212
 213notify_otg:
 214        if (notify_otg) {
 215                /*
 216                 * If VBUS is absent Connect D+/D- lines to PMIC for BC
 217                 * detection. Else connect them to SOC for USB communication.
 218                 */
 219                if (info->pdata->gpio_mux_cntl)
 220                        gpiod_set_value(info->pdata->gpio_mux_cntl,
 221                                vbus_attach ? EXTCON_GPIO_MUX_SEL_SOC
 222                                                : EXTCON_GPIO_MUX_SEL_PMIC);
 223
 224                atomic_notifier_call_chain(&info->otg->notifier,
 225                        vbus_attach ? USB_EVENT_VBUS : USB_EVENT_NONE, NULL);
 226        }
 227
 228        if (notify_charger)
 229                extcon_set_state_sync(info->edev, cable, vbus_attach);
 230
 231        /* Clear the flags on disconnect event */
 232        if (!vbus_attach)
 233                notify_otg = notify_charger = false;
 234
 235        return 0;
 236
 237dev_det_ret:
 238        if (ret < 0)
 239                dev_err(info->dev, "failed to detect BC Mod\n");
 240
 241        return ret;
 242}
 243
 244static irqreturn_t axp288_extcon_isr(int irq, void *data)
 245{
 246        struct axp288_extcon_info *info = data;
 247        int ret;
 248
 249        ret = axp288_handle_chrg_det_event(info);
 250        if (ret < 0)
 251                dev_err(info->dev, "failed to handle the interrupt\n");
 252
 253        return IRQ_HANDLED;
 254}
 255
 256static void axp288_extcon_enable_irq(struct axp288_extcon_info *info)
 257{
 258        /* Unmask VBUS interrupt */
 259        regmap_write(info->regmap, AXP288_PWRSRC_IRQ_CFG_REG,
 260                                                PWRSRC_IRQ_CFG_MASK);
 261        regmap_update_bits(info->regmap, AXP288_BC_GLOBAL_REG,
 262                                                BC_GLOBAL_RUN, 0);
 263        /* Unmask the BC1.2 complete interrupts */
 264        regmap_write(info->regmap, AXP288_BC12_IRQ_CFG_REG, BC12_IRQ_CFG_MASK);
 265        /* Enable the charger detection logic */
 266        regmap_update_bits(info->regmap, AXP288_BC_GLOBAL_REG,
 267                                        BC_GLOBAL_RUN, BC_GLOBAL_RUN);
 268}
 269
 270static int axp288_extcon_probe(struct platform_device *pdev)
 271{
 272        struct axp288_extcon_info *info;
 273        struct axp20x_dev *axp20x = dev_get_drvdata(pdev->dev.parent);
 274        int ret, i, pirq, gpio;
 275
 276        info = devm_kzalloc(&pdev->dev, sizeof(*info), GFP_KERNEL);
 277        if (!info)
 278                return -ENOMEM;
 279
 280        info->dev = &pdev->dev;
 281        info->regmap = axp20x->regmap;
 282        info->regmap_irqc = axp20x->regmap_irqc;
 283        info->pdata = pdev->dev.platform_data;
 284
 285        if (!info->pdata) {
 286                /* Try ACPI provided pdata via device properties */
 287                if (!device_property_present(&pdev->dev,
 288                                        "axp288_extcon_data\n"))
 289                        dev_err(&pdev->dev, "failed to get platform data\n");
 290                return -ENODEV;
 291        }
 292        platform_set_drvdata(pdev, info);
 293
 294        axp288_extcon_log_rsi(info);
 295
 296        /* Initialize extcon device */
 297        info->edev = devm_extcon_dev_allocate(&pdev->dev,
 298                                              axp288_extcon_cables);
 299        if (IS_ERR(info->edev)) {
 300                dev_err(&pdev->dev, "failed to allocate memory for extcon\n");
 301                return PTR_ERR(info->edev);
 302        }
 303
 304        /* Register extcon device */
 305        ret = devm_extcon_dev_register(&pdev->dev, info->edev);
 306        if (ret) {
 307                dev_err(&pdev->dev, "failed to register extcon device\n");
 308                return ret;
 309        }
 310
 311        /* Get otg transceiver phy */
 312        info->otg = devm_usb_get_phy(&pdev->dev, USB_PHY_TYPE_USB2);
 313        if (IS_ERR(info->otg)) {
 314                dev_err(&pdev->dev, "failed to get otg transceiver\n");
 315                return PTR_ERR(info->otg);
 316        }
 317
 318        /* Set up gpio control for USB Mux */
 319        if (info->pdata->gpio_mux_cntl) {
 320                gpio = desc_to_gpio(info->pdata->gpio_mux_cntl);
 321                ret = devm_gpio_request(&pdev->dev, gpio, "USB_MUX");
 322                if (ret < 0) {
 323                        dev_err(&pdev->dev,
 324                                "failed to request the gpio=%d\n", gpio);
 325                        return ret;
 326                }
 327                gpiod_direction_output(info->pdata->gpio_mux_cntl,
 328                                                EXTCON_GPIO_MUX_SEL_PMIC);
 329        }
 330
 331        for (i = 0; i < EXTCON_IRQ_END; i++) {
 332                pirq = platform_get_irq(pdev, i);
 333                info->irq[i] = regmap_irq_get_virq(info->regmap_irqc, pirq);
 334                if (info->irq[i] < 0) {
 335                        dev_err(&pdev->dev,
 336                                "failed to get virtual interrupt=%d\n", pirq);
 337                        ret = info->irq[i];
 338                        return ret;
 339                }
 340
 341                ret = devm_request_threaded_irq(&pdev->dev, info->irq[i],
 342                                NULL, axp288_extcon_isr,
 343                                IRQF_ONESHOT | IRQF_NO_SUSPEND,
 344                                pdev->name, info);
 345                if (ret) {
 346                        dev_err(&pdev->dev, "failed to request interrupt=%d\n",
 347                                                        info->irq[i]);
 348                        return ret;
 349                }
 350        }
 351
 352        /* Enable interrupts */
 353        axp288_extcon_enable_irq(info);
 354
 355        return 0;
 356}
 357
 358static struct platform_driver axp288_extcon_driver = {
 359        .probe = axp288_extcon_probe,
 360        .driver = {
 361                .name = "axp288_extcon",
 362        },
 363};
 364module_platform_driver(axp288_extcon_driver);
 365
 366MODULE_AUTHOR("Ramakrishna Pallala <ramakrishna.pallala@intel.com>");
 367MODULE_DESCRIPTION("X-Powers AXP288 extcon driver");
 368MODULE_LICENSE("GPL v2");
 369