linux/drivers/extcon/extcon-intel-mrfld.c
<<
>>
Prefs
   1// SPDX-License-Identifier: GPL-2.0
   2/*
   3 * extcon driver for Basin Cove PMIC
   4 *
   5 * Copyright (c) 2019, Intel Corporation.
   6 * Author: Andy Shevchenko <andriy.shevchenko@linux.intel.com>
   7 */
   8
   9#include <linux/extcon-provider.h>
  10#include <linux/interrupt.h>
  11#include <linux/mfd/intel_soc_pmic.h>
  12#include <linux/mfd/intel_soc_pmic_mrfld.h>
  13#include <linux/mod_devicetable.h>
  14#include <linux/module.h>
  15#include <linux/platform_device.h>
  16#include <linux/regmap.h>
  17
  18#include "extcon-intel.h"
  19
  20#define BCOVE_USBIDCTRL                 0x19
  21#define BCOVE_USBIDCTRL_ID              BIT(0)
  22#define BCOVE_USBIDCTRL_ACA             BIT(1)
  23#define BCOVE_USBIDCTRL_ALL     (BCOVE_USBIDCTRL_ID | BCOVE_USBIDCTRL_ACA)
  24
  25#define BCOVE_USBIDSTS                  0x1a
  26#define BCOVE_USBIDSTS_GND              BIT(0)
  27#define BCOVE_USBIDSTS_RARBRC_MASK      GENMASK(2, 1)
  28#define BCOVE_USBIDSTS_RARBRC_SHIFT     1
  29#define BCOVE_USBIDSTS_NO_ACA           0
  30#define BCOVE_USBIDSTS_R_ID_A           1
  31#define BCOVE_USBIDSTS_R_ID_B           2
  32#define BCOVE_USBIDSTS_R_ID_C           3
  33#define BCOVE_USBIDSTS_FLOAT            BIT(3)
  34#define BCOVE_USBIDSTS_SHORT            BIT(4)
  35
  36#define BCOVE_CHGRIRQ_ALL       (BCOVE_CHGRIRQ_VBUSDET | BCOVE_CHGRIRQ_DCDET | \
  37                                 BCOVE_CHGRIRQ_BATTDET | BCOVE_CHGRIRQ_USBIDDET)
  38
  39#define BCOVE_CHGRCTRL0                 0x4b
  40#define BCOVE_CHGRCTRL0_CHGRRESET       BIT(0)
  41#define BCOVE_CHGRCTRL0_EMRGCHREN       BIT(1)
  42#define BCOVE_CHGRCTRL0_EXTCHRDIS       BIT(2)
  43#define BCOVE_CHGRCTRL0_SWCONTROL       BIT(3)
  44#define BCOVE_CHGRCTRL0_TTLCK           BIT(4)
  45#define BCOVE_CHGRCTRL0_BIT_5           BIT(5)
  46#define BCOVE_CHGRCTRL0_BIT_6           BIT(6)
  47#define BCOVE_CHGRCTRL0_CHR_WDT_NOKICK  BIT(7)
  48
  49struct mrfld_extcon_data {
  50        struct device *dev;
  51        struct regmap *regmap;
  52        struct extcon_dev *edev;
  53        unsigned int status;
  54        unsigned int id;
  55};
  56
  57static const unsigned int mrfld_extcon_cable[] = {
  58        EXTCON_USB,
  59        EXTCON_USB_HOST,
  60        EXTCON_CHG_USB_SDP,
  61        EXTCON_CHG_USB_CDP,
  62        EXTCON_CHG_USB_DCP,
  63        EXTCON_CHG_USB_ACA,
  64        EXTCON_NONE,
  65};
  66
  67static int mrfld_extcon_clear(struct mrfld_extcon_data *data, unsigned int reg,
  68                              unsigned int mask)
  69{
  70        return regmap_update_bits(data->regmap, reg, mask, 0x00);
  71}
  72
  73static int mrfld_extcon_set(struct mrfld_extcon_data *data, unsigned int reg,
  74                            unsigned int mask)
  75{
  76        return regmap_update_bits(data->regmap, reg, mask, 0xff);
  77}
  78
  79static int mrfld_extcon_sw_control(struct mrfld_extcon_data *data, bool enable)
  80{
  81        unsigned int mask = BCOVE_CHGRCTRL0_SWCONTROL;
  82        struct device *dev = data->dev;
  83        int ret;
  84
  85        if (enable)
  86                ret = mrfld_extcon_set(data, BCOVE_CHGRCTRL0, mask);
  87        else
  88                ret = mrfld_extcon_clear(data, BCOVE_CHGRCTRL0, mask);
  89        if (ret)
  90                dev_err(dev, "can't set SW control: %d\n", ret);
  91        return ret;
  92}
  93
  94static int mrfld_extcon_get_id(struct mrfld_extcon_data *data)
  95{
  96        struct regmap *regmap = data->regmap;
  97        unsigned int id;
  98        bool ground;
  99        int ret;
 100
 101        ret = regmap_read(regmap, BCOVE_USBIDSTS, &id);
 102        if (ret)
 103                return ret;
 104
 105        if (id & BCOVE_USBIDSTS_FLOAT)
 106                return INTEL_USB_ID_FLOAT;
 107
 108        switch ((id & BCOVE_USBIDSTS_RARBRC_MASK) >> BCOVE_USBIDSTS_RARBRC_SHIFT) {
 109        case BCOVE_USBIDSTS_R_ID_A:
 110                return INTEL_USB_RID_A;
 111        case BCOVE_USBIDSTS_R_ID_B:
 112                return INTEL_USB_RID_B;
 113        case BCOVE_USBIDSTS_R_ID_C:
 114                return INTEL_USB_RID_C;
 115        }
 116
 117        /*
 118         * PMIC A0 reports USBIDSTS_GND = 1 for ID_GND,
 119         * but PMIC B0 reports USBIDSTS_GND = 0 for ID_GND.
 120         * Thus we must check this bit at last.
 121         */
 122        ground = id & BCOVE_USBIDSTS_GND;
 123        switch ('A' + BCOVE_MAJOR(data->id)) {
 124        case 'A':
 125                return ground ? INTEL_USB_ID_GND : INTEL_USB_ID_FLOAT;
 126        case 'B':
 127                return ground ? INTEL_USB_ID_FLOAT : INTEL_USB_ID_GND;
 128        }
 129
 130        /* Unknown or unsupported type */
 131        return INTEL_USB_ID_FLOAT;
 132}
 133
 134static int mrfld_extcon_role_detect(struct mrfld_extcon_data *data)
 135{
 136        unsigned int id;
 137        bool usb_host;
 138        int ret;
 139
 140        ret = mrfld_extcon_get_id(data);
 141        if (ret < 0)
 142                return ret;
 143
 144        id = ret;
 145
 146        usb_host = (id == INTEL_USB_ID_GND) || (id == INTEL_USB_RID_A);
 147        extcon_set_state_sync(data->edev, EXTCON_USB_HOST, usb_host);
 148
 149        return 0;
 150}
 151
 152static int mrfld_extcon_cable_detect(struct mrfld_extcon_data *data)
 153{
 154        struct regmap *regmap = data->regmap;
 155        unsigned int status, change;
 156        int ret;
 157
 158        /*
 159         * It seems SCU firmware clears the content of BCOVE_CHGRIRQ1
 160         * and makes it useless for OS. Instead we compare a previously
 161         * stored status to the current one, provided by BCOVE_SCHGRIRQ1.
 162         */
 163        ret = regmap_read(regmap, BCOVE_SCHGRIRQ1, &status);
 164        if (ret)
 165                return ret;
 166
 167        change = status ^ data->status;
 168        if (!change)
 169                return -ENODATA;
 170
 171        if (change & BCOVE_CHGRIRQ_USBIDDET) {
 172                ret = mrfld_extcon_role_detect(data);
 173                if (ret)
 174                        return ret;
 175        }
 176
 177        data->status = status;
 178
 179        return 0;
 180}
 181
 182static irqreturn_t mrfld_extcon_interrupt(int irq, void *dev_id)
 183{
 184        struct mrfld_extcon_data *data = dev_id;
 185        int ret;
 186
 187        ret = mrfld_extcon_cable_detect(data);
 188
 189        mrfld_extcon_clear(data, BCOVE_MIRQLVL1, BCOVE_LVL1_CHGR);
 190
 191        return ret ? IRQ_NONE: IRQ_HANDLED;
 192}
 193
 194static int mrfld_extcon_probe(struct platform_device *pdev)
 195{
 196        struct device *dev = &pdev->dev;
 197        struct intel_soc_pmic *pmic = dev_get_drvdata(dev->parent);
 198        struct regmap *regmap = pmic->regmap;
 199        struct mrfld_extcon_data *data;
 200        unsigned int id;
 201        int irq, ret;
 202
 203        irq = platform_get_irq(pdev, 0);
 204        if (irq < 0)
 205                return irq;
 206
 207        data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL);
 208        if (!data)
 209                return -ENOMEM;
 210
 211        data->dev = dev;
 212        data->regmap = regmap;
 213
 214        data->edev = devm_extcon_dev_allocate(dev, mrfld_extcon_cable);
 215        if (IS_ERR(data->edev))
 216                return -ENOMEM;
 217
 218        ret = devm_extcon_dev_register(dev, data->edev);
 219        if (ret < 0) {
 220                dev_err(dev, "can't register extcon device: %d\n", ret);
 221                return ret;
 222        }
 223
 224        ret = devm_request_threaded_irq(dev, irq, NULL, mrfld_extcon_interrupt,
 225                                        IRQF_ONESHOT | IRQF_SHARED, pdev->name,
 226                                        data);
 227        if (ret) {
 228                dev_err(dev, "can't register IRQ handler: %d\n", ret);
 229                return ret;
 230        }
 231
 232        ret = regmap_read(regmap, BCOVE_ID, &id);
 233        if (ret) {
 234                dev_err(dev, "can't read PMIC ID: %d\n", ret);
 235                return ret;
 236        }
 237
 238        data->id = id;
 239
 240        ret = mrfld_extcon_sw_control(data, true);
 241        if (ret)
 242                return ret;
 243
 244        /* Get initial state */
 245        mrfld_extcon_role_detect(data);
 246
 247        mrfld_extcon_clear(data, BCOVE_MIRQLVL1, BCOVE_LVL1_CHGR);
 248        mrfld_extcon_clear(data, BCOVE_MCHGRIRQ1, BCOVE_CHGRIRQ_ALL);
 249
 250        mrfld_extcon_set(data, BCOVE_USBIDCTRL, BCOVE_USBIDCTRL_ALL);
 251
 252        platform_set_drvdata(pdev, data);
 253
 254        return 0;
 255}
 256
 257static int mrfld_extcon_remove(struct platform_device *pdev)
 258{
 259        struct mrfld_extcon_data *data = platform_get_drvdata(pdev);
 260
 261        mrfld_extcon_sw_control(data, false);
 262
 263        return 0;
 264}
 265
 266static const struct platform_device_id mrfld_extcon_id_table[] = {
 267        { .name = "mrfld_bcove_pwrsrc" },
 268        {}
 269};
 270MODULE_DEVICE_TABLE(platform, mrfld_extcon_id_table);
 271
 272static struct platform_driver mrfld_extcon_driver = {
 273        .driver = {
 274                .name   = "mrfld_bcove_pwrsrc",
 275        },
 276        .probe          = mrfld_extcon_probe,
 277        .remove         = mrfld_extcon_remove,
 278        .id_table       = mrfld_extcon_id_table,
 279};
 280module_platform_driver(mrfld_extcon_driver);
 281
 282MODULE_AUTHOR("Andy Shevchenko <andriy.shevchenko@linux.intel.com>");
 283MODULE_DESCRIPTION("extcon driver for Intel Merrifield Basin Cove PMIC");
 284MODULE_LICENSE("GPL v2");
 285