linux/drivers/extcon/extcon-ptn5150.c
<<
>>
Prefs
   1// SPDX-License-Identifier: GPL-2.0+
   2//
   3// extcon-ptn5150.c - PTN5150 CC logic extcon driver to support USB detection
   4//
   5// Based on extcon-sm5502.c driver
   6// Copyright (c) 2018-2019 by Vijai Kumar K
   7// Author: Vijai Kumar K <vijaikumar.kanagarajan@gmail.com>
   8// Copyright (c) 2020 Krzysztof Kozlowski <krzk@kernel.org>
   9
  10#include <linux/bitfield.h>
  11#include <linux/err.h>
  12#include <linux/i2c.h>
  13#include <linux/interrupt.h>
  14#include <linux/kernel.h>
  15#include <linux/module.h>
  16#include <linux/regmap.h>
  17#include <linux/slab.h>
  18#include <linux/extcon-provider.h>
  19#include <linux/gpio/consumer.h>
  20
  21/* PTN5150 registers */
  22#define PTN5150_REG_DEVICE_ID                   0x01
  23#define PTN5150_REG_CONTROL                     0x02
  24#define PTN5150_REG_INT_STATUS                  0x03
  25#define PTN5150_REG_CC_STATUS                   0x04
  26#define PTN5150_REG_CON_DET                     0x09
  27#define PTN5150_REG_VCONN_STATUS                0x0a
  28#define PTN5150_REG_RESET                       0x0b
  29#define PTN5150_REG_INT_MASK                    0x18
  30#define PTN5150_REG_INT_REG_STATUS              0x19
  31#define PTN5150_REG_END                         PTN5150_REG_INT_REG_STATUS
  32
  33#define PTN5150_DFP_ATTACHED                    0x1
  34#define PTN5150_UFP_ATTACHED                    0x2
  35
  36/* Define PTN5150 MASK/SHIFT constant */
  37#define PTN5150_REG_DEVICE_ID_VERSION           GENMASK(7, 3)
  38#define PTN5150_REG_DEVICE_ID_VENDOR            GENMASK(2, 0)
  39
  40#define PTN5150_REG_CC_PORT_ATTACHMENT          GENMASK(4, 2)
  41#define PTN5150_REG_CC_VBUS_DETECTION           BIT(7)
  42#define PTN5150_REG_INT_CABLE_ATTACH_MASK       BIT(0)
  43#define PTN5150_REG_INT_CABLE_DETACH_MASK       BIT(1)
  44
  45struct ptn5150_info {
  46        struct device *dev;
  47        struct extcon_dev *edev;
  48        struct i2c_client *i2c;
  49        struct regmap *regmap;
  50        struct gpio_desc *int_gpiod;
  51        struct gpio_desc *vbus_gpiod;
  52        int irq;
  53        struct work_struct irq_work;
  54        struct mutex mutex;
  55};
  56
  57/* List of detectable cables */
  58static const unsigned int ptn5150_extcon_cable[] = {
  59        EXTCON_USB,
  60        EXTCON_USB_HOST,
  61        EXTCON_NONE,
  62};
  63
  64static const struct regmap_config ptn5150_regmap_config = {
  65        .reg_bits       = 8,
  66        .val_bits       = 8,
  67        .max_register   = PTN5150_REG_END,
  68};
  69
  70static void ptn5150_check_state(struct ptn5150_info *info)
  71{
  72        unsigned int port_status, reg_data, vbus;
  73        int ret;
  74
  75        ret = regmap_read(info->regmap, PTN5150_REG_CC_STATUS, &reg_data);
  76        if (ret) {
  77                dev_err(info->dev, "failed to read CC STATUS %d\n", ret);
  78                return;
  79        }
  80
  81        port_status = FIELD_GET(PTN5150_REG_CC_PORT_ATTACHMENT, reg_data);
  82
  83        switch (port_status) {
  84        case PTN5150_DFP_ATTACHED:
  85                extcon_set_state_sync(info->edev, EXTCON_USB_HOST, false);
  86                gpiod_set_value_cansleep(info->vbus_gpiod, 0);
  87                extcon_set_state_sync(info->edev, EXTCON_USB, true);
  88                break;
  89        case PTN5150_UFP_ATTACHED:
  90                extcon_set_state_sync(info->edev, EXTCON_USB, false);
  91                vbus = FIELD_GET(PTN5150_REG_CC_VBUS_DETECTION, reg_data);
  92                if (vbus)
  93                        gpiod_set_value_cansleep(info->vbus_gpiod, 0);
  94                else
  95                        gpiod_set_value_cansleep(info->vbus_gpiod, 1);
  96
  97                extcon_set_state_sync(info->edev, EXTCON_USB_HOST, true);
  98                break;
  99        default:
 100                break;
 101        }
 102}
 103
 104static void ptn5150_irq_work(struct work_struct *work)
 105{
 106        struct ptn5150_info *info = container_of(work,
 107                        struct ptn5150_info, irq_work);
 108        int ret = 0;
 109        unsigned int int_status;
 110
 111        if (!info->edev)
 112                return;
 113
 114        mutex_lock(&info->mutex);
 115
 116        /* Clear interrupt. Read would clear the register */
 117        ret = regmap_read(info->regmap, PTN5150_REG_INT_STATUS, &int_status);
 118        if (ret) {
 119                dev_err(info->dev, "failed to read INT STATUS %d\n", ret);
 120                mutex_unlock(&info->mutex);
 121                return;
 122        }
 123
 124        if (int_status) {
 125                unsigned int cable_attach;
 126
 127                cable_attach = int_status & PTN5150_REG_INT_CABLE_ATTACH_MASK;
 128                if (cable_attach) {
 129                        ptn5150_check_state(info);
 130                } else {
 131                        extcon_set_state_sync(info->edev,
 132                                        EXTCON_USB_HOST, false);
 133                        extcon_set_state_sync(info->edev,
 134                                        EXTCON_USB, false);
 135                        gpiod_set_value_cansleep(info->vbus_gpiod, 0);
 136                }
 137        }
 138
 139        /* Clear interrupt. Read would clear the register */
 140        ret = regmap_read(info->regmap, PTN5150_REG_INT_REG_STATUS,
 141                        &int_status);
 142        if (ret) {
 143                dev_err(info->dev,
 144                        "failed to read INT REG STATUS %d\n", ret);
 145                mutex_unlock(&info->mutex);
 146                return;
 147        }
 148
 149        mutex_unlock(&info->mutex);
 150}
 151
 152
 153static irqreturn_t ptn5150_irq_handler(int irq, void *data)
 154{
 155        struct ptn5150_info *info = data;
 156
 157        schedule_work(&info->irq_work);
 158
 159        return IRQ_HANDLED;
 160}
 161
 162static int ptn5150_init_dev_type(struct ptn5150_info *info)
 163{
 164        unsigned int reg_data, vendor_id, version_id;
 165        int ret;
 166
 167        ret = regmap_read(info->regmap, PTN5150_REG_DEVICE_ID, &reg_data);
 168        if (ret) {
 169                dev_err(info->dev, "failed to read DEVICE_ID %d\n", ret);
 170                return -EINVAL;
 171        }
 172
 173        vendor_id = FIELD_GET(PTN5150_REG_DEVICE_ID_VENDOR, reg_data);
 174        version_id = FIELD_GET(PTN5150_REG_DEVICE_ID_VERSION, reg_data);
 175        dev_dbg(info->dev, "Device type: version: 0x%x, vendor: 0x%x\n",
 176                version_id, vendor_id);
 177
 178        /* Clear any existing interrupts */
 179        ret = regmap_read(info->regmap, PTN5150_REG_INT_STATUS, &reg_data);
 180        if (ret) {
 181                dev_err(info->dev,
 182                        "failed to read PTN5150_REG_INT_STATUS %d\n",
 183                        ret);
 184                return -EINVAL;
 185        }
 186
 187        ret = regmap_read(info->regmap, PTN5150_REG_INT_REG_STATUS, &reg_data);
 188        if (ret) {
 189                dev_err(info->dev,
 190                        "failed to read PTN5150_REG_INT_REG_STATUS %d\n", ret);
 191                return -EINVAL;
 192        }
 193
 194        return 0;
 195}
 196
 197static int ptn5150_i2c_probe(struct i2c_client *i2c)
 198{
 199        struct device *dev = &i2c->dev;
 200        struct device_node *np = i2c->dev.of_node;
 201        struct ptn5150_info *info;
 202        int ret;
 203
 204        if (!np)
 205                return -EINVAL;
 206
 207        info = devm_kzalloc(&i2c->dev, sizeof(*info), GFP_KERNEL);
 208        if (!info)
 209                return -ENOMEM;
 210        i2c_set_clientdata(i2c, info);
 211
 212        info->dev = &i2c->dev;
 213        info->i2c = i2c;
 214        info->vbus_gpiod = devm_gpiod_get(&i2c->dev, "vbus", GPIOD_OUT_LOW);
 215        if (IS_ERR(info->vbus_gpiod)) {
 216                ret = PTR_ERR(info->vbus_gpiod);
 217                if (ret == -ENOENT) {
 218                        dev_info(dev, "No VBUS GPIO, ignoring VBUS control\n");
 219                        info->vbus_gpiod = NULL;
 220                } else {
 221                        return dev_err_probe(dev, ret, "failed to get VBUS GPIO\n");
 222                }
 223        }
 224
 225        mutex_init(&info->mutex);
 226
 227        INIT_WORK(&info->irq_work, ptn5150_irq_work);
 228
 229        info->regmap = devm_regmap_init_i2c(i2c, &ptn5150_regmap_config);
 230        if (IS_ERR(info->regmap)) {
 231                return dev_err_probe(info->dev, PTR_ERR(info->regmap),
 232                                     "failed to allocate register map\n");
 233        }
 234
 235        if (i2c->irq > 0) {
 236                info->irq = i2c->irq;
 237        } else {
 238                info->int_gpiod = devm_gpiod_get(&i2c->dev, "int", GPIOD_IN);
 239                if (IS_ERR(info->int_gpiod)) {
 240                        return dev_err_probe(dev, PTR_ERR(info->int_gpiod),
 241                                             "failed to get INT GPIO\n");
 242                }
 243
 244                info->irq = gpiod_to_irq(info->int_gpiod);
 245                if (info->irq < 0) {
 246                        dev_err(dev, "failed to get INTB IRQ\n");
 247                        return info->irq;
 248                }
 249        }
 250
 251        ret = devm_request_threaded_irq(dev, info->irq, NULL,
 252                                        ptn5150_irq_handler,
 253                                        IRQF_TRIGGER_FALLING |
 254                                        IRQF_ONESHOT,
 255                                        i2c->name, info);
 256        if (ret < 0) {
 257                dev_err(dev, "failed to request handler for INTB IRQ\n");
 258                return ret;
 259        }
 260
 261        /* Allocate extcon device */
 262        info->edev = devm_extcon_dev_allocate(info->dev, ptn5150_extcon_cable);
 263        if (IS_ERR(info->edev)) {
 264                dev_err(info->dev, "failed to allocate memory for extcon\n");
 265                return -ENOMEM;
 266        }
 267
 268        /* Register extcon device */
 269        ret = devm_extcon_dev_register(info->dev, info->edev);
 270        if (ret) {
 271                dev_err(info->dev, "failed to register extcon device\n");
 272                return ret;
 273        }
 274
 275        extcon_set_property_capability(info->edev, EXTCON_USB,
 276                                        EXTCON_PROP_USB_VBUS);
 277        extcon_set_property_capability(info->edev, EXTCON_USB_HOST,
 278                                        EXTCON_PROP_USB_VBUS);
 279        extcon_set_property_capability(info->edev, EXTCON_USB_HOST,
 280                                        EXTCON_PROP_USB_TYPEC_POLARITY);
 281
 282        /* Initialize PTN5150 device and print vendor id and version id */
 283        ret = ptn5150_init_dev_type(info);
 284        if (ret)
 285                return -EINVAL;
 286
 287        /*
 288         * Update current extcon state if for example OTG connection was there
 289         * before the probe
 290         */
 291        mutex_lock(&info->mutex);
 292        ptn5150_check_state(info);
 293        mutex_unlock(&info->mutex);
 294
 295        return 0;
 296}
 297
 298static const struct of_device_id ptn5150_dt_match[] = {
 299        { .compatible = "nxp,ptn5150" },
 300        { },
 301};
 302MODULE_DEVICE_TABLE(of, ptn5150_dt_match);
 303
 304static const struct i2c_device_id ptn5150_i2c_id[] = {
 305        { "ptn5150", 0 },
 306        { }
 307};
 308MODULE_DEVICE_TABLE(i2c, ptn5150_i2c_id);
 309
 310static struct i2c_driver ptn5150_i2c_driver = {
 311        .driver         = {
 312                .name   = "ptn5150",
 313                .of_match_table = ptn5150_dt_match,
 314        },
 315        .probe_new      = ptn5150_i2c_probe,
 316        .id_table = ptn5150_i2c_id,
 317};
 318module_i2c_driver(ptn5150_i2c_driver);
 319
 320MODULE_DESCRIPTION("NXP PTN5150 CC logic Extcon driver");
 321MODULE_AUTHOR("Vijai Kumar K <vijaikumar.kanagarajan@gmail.com>");
 322MODULE_AUTHOR("Krzysztof Kozlowski <krzk@kernel.org>");
 323MODULE_LICENSE("GPL v2");
 324