14ed754deSVijai Kumar K // SPDX-License-Identifier: GPL-2.0+ 24ed754deSVijai Kumar K // 34ed754deSVijai Kumar K // extcon-ptn5150.c - PTN5150 CC logic extcon driver to support USB detection 44ed754deSVijai Kumar K // 54ed754deSVijai Kumar K // Based on extcon-sm5502.c driver 64ed754deSVijai Kumar K // Copyright (c) 2018-2019 by Vijai Kumar K 74ed754deSVijai Kumar K // Author: Vijai Kumar K <vijaikumar.kanagarajan@gmail.com> 84ed754deSVijai Kumar K 94ed754deSVijai Kumar K #include <linux/err.h> 104ed754deSVijai Kumar K #include <linux/i2c.h> 114ed754deSVijai Kumar K #include <linux/interrupt.h> 124ed754deSVijai Kumar K #include <linux/kernel.h> 134ed754deSVijai Kumar K #include <linux/module.h> 144ed754deSVijai Kumar K #include <linux/regmap.h> 154ed754deSVijai Kumar K #include <linux/slab.h> 164ed754deSVijai Kumar K #include <linux/extcon-provider.h> 174ed754deSVijai Kumar K #include <linux/gpio/consumer.h> 184ed754deSVijai Kumar K 194ed754deSVijai Kumar K /* PTN5150 registers */ 204ed754deSVijai Kumar K enum ptn5150_reg { 214ed754deSVijai Kumar K PTN5150_REG_DEVICE_ID = 0x01, 224ed754deSVijai Kumar K PTN5150_REG_CONTROL, 234ed754deSVijai Kumar K PTN5150_REG_INT_STATUS, 244ed754deSVijai Kumar K PTN5150_REG_CC_STATUS, 254ed754deSVijai Kumar K PTN5150_REG_CON_DET = 0x09, 264ed754deSVijai Kumar K PTN5150_REG_VCONN_STATUS, 274ed754deSVijai Kumar K PTN5150_REG_RESET, 284ed754deSVijai Kumar K PTN5150_REG_INT_MASK = 0x18, 294ed754deSVijai Kumar K PTN5150_REG_INT_REG_STATUS, 304ed754deSVijai Kumar K PTN5150_REG_END, 314ed754deSVijai Kumar K }; 324ed754deSVijai Kumar K 334ed754deSVijai Kumar K #define PTN5150_DFP_ATTACHED 0x1 344ed754deSVijai Kumar K #define PTN5150_UFP_ATTACHED 0x2 354ed754deSVijai Kumar K 364ed754deSVijai Kumar K /* Define PTN5150 MASK/SHIFT constant */ 374ed754deSVijai Kumar K #define PTN5150_REG_DEVICE_ID_VENDOR_SHIFT 0 384ed754deSVijai Kumar K #define PTN5150_REG_DEVICE_ID_VENDOR_MASK \ 394ed754deSVijai Kumar K (0x3 << PTN5150_REG_DEVICE_ID_VENDOR_SHIFT) 404ed754deSVijai Kumar K 414ed754deSVijai Kumar K #define PTN5150_REG_DEVICE_ID_VERSION_SHIFT 3 424ed754deSVijai Kumar K #define PTN5150_REG_DEVICE_ID_VERSION_MASK \ 434ed754deSVijai Kumar K (0x1f << PTN5150_REG_DEVICE_ID_VERSION_SHIFT) 444ed754deSVijai Kumar K 454ed754deSVijai Kumar K #define PTN5150_REG_CC_PORT_ATTACHMENT_SHIFT 2 464ed754deSVijai Kumar K #define PTN5150_REG_CC_PORT_ATTACHMENT_MASK \ 474ed754deSVijai Kumar K (0x7 << PTN5150_REG_CC_PORT_ATTACHMENT_SHIFT) 484ed754deSVijai Kumar K 494ed754deSVijai Kumar K #define PTN5150_REG_CC_VBUS_DETECTION_SHIFT 7 504ed754deSVijai Kumar K #define PTN5150_REG_CC_VBUS_DETECTION_MASK \ 514ed754deSVijai Kumar K (0x1 << PTN5150_REG_CC_VBUS_DETECTION_SHIFT) 524ed754deSVijai Kumar K 534ed754deSVijai Kumar K #define PTN5150_REG_INT_CABLE_ATTACH_SHIFT 0 544ed754deSVijai Kumar K #define PTN5150_REG_INT_CABLE_ATTACH_MASK \ 554ed754deSVijai Kumar K (0x1 << PTN5150_REG_INT_CABLE_ATTACH_SHIFT) 564ed754deSVijai Kumar K 574ed754deSVijai Kumar K #define PTN5150_REG_INT_CABLE_DETACH_SHIFT 1 584ed754deSVijai Kumar K #define PTN5150_REG_INT_CABLE_DETACH_MASK \ 594ed754deSVijai Kumar K (0x1 << PTN5150_REG_CC_CABLE_DETACH_SHIFT) 604ed754deSVijai Kumar K 614ed754deSVijai Kumar K struct ptn5150_info { 624ed754deSVijai Kumar K struct device *dev; 634ed754deSVijai Kumar K struct extcon_dev *edev; 644ed754deSVijai Kumar K struct i2c_client *i2c; 654ed754deSVijai Kumar K struct regmap *regmap; 664ed754deSVijai Kumar K struct gpio_desc *int_gpiod; 674ed754deSVijai Kumar K struct gpio_desc *vbus_gpiod; 684ed754deSVijai Kumar K int irq; 694ed754deSVijai Kumar K struct work_struct irq_work; 704ed754deSVijai Kumar K struct mutex mutex; 714ed754deSVijai Kumar K }; 724ed754deSVijai Kumar K 734ed754deSVijai Kumar K /* List of detectable cables */ 744ed754deSVijai Kumar K static const unsigned int ptn5150_extcon_cable[] = { 754ed754deSVijai Kumar K EXTCON_USB, 764ed754deSVijai Kumar K EXTCON_USB_HOST, 774ed754deSVijai Kumar K EXTCON_NONE, 784ed754deSVijai Kumar K }; 794ed754deSVijai Kumar K 804ed754deSVijai Kumar K static const struct regmap_config ptn5150_regmap_config = { 814ed754deSVijai Kumar K .reg_bits = 8, 824ed754deSVijai Kumar K .val_bits = 8, 834ed754deSVijai Kumar K .max_register = PTN5150_REG_END, 844ed754deSVijai Kumar K }; 854ed754deSVijai Kumar K 864ed754deSVijai Kumar K static void ptn5150_irq_work(struct work_struct *work) 874ed754deSVijai Kumar K { 884ed754deSVijai Kumar K struct ptn5150_info *info = container_of(work, 894ed754deSVijai Kumar K struct ptn5150_info, irq_work); 904ed754deSVijai Kumar K int ret = 0; 914ed754deSVijai Kumar K unsigned int reg_data; 924ed754deSVijai Kumar K unsigned int int_status; 934ed754deSVijai Kumar K 944ed754deSVijai Kumar K if (!info->edev) 954ed754deSVijai Kumar K return; 964ed754deSVijai Kumar K 974ed754deSVijai Kumar K mutex_lock(&info->mutex); 984ed754deSVijai Kumar K 994ed754deSVijai Kumar K ret = regmap_read(info->regmap, PTN5150_REG_CC_STATUS, ®_data); 1004ed754deSVijai Kumar K if (ret) { 1014ed754deSVijai Kumar K dev_err(info->dev, "failed to read CC STATUS %d\n", ret); 1024ed754deSVijai Kumar K mutex_unlock(&info->mutex); 1034ed754deSVijai Kumar K return; 1044ed754deSVijai Kumar K } 1054ed754deSVijai Kumar K 1064ed754deSVijai Kumar K /* Clear interrupt. Read would clear the register */ 1074ed754deSVijai Kumar K ret = regmap_read(info->regmap, PTN5150_REG_INT_STATUS, &int_status); 1084ed754deSVijai Kumar K if (ret) { 1094ed754deSVijai Kumar K dev_err(info->dev, "failed to read INT STATUS %d\n", ret); 1104ed754deSVijai Kumar K mutex_unlock(&info->mutex); 1114ed754deSVijai Kumar K return; 1124ed754deSVijai Kumar K } 1134ed754deSVijai Kumar K 1144ed754deSVijai Kumar K if (int_status) { 1154ed754deSVijai Kumar K unsigned int cable_attach; 1164ed754deSVijai Kumar K 1174ed754deSVijai Kumar K cable_attach = int_status & PTN5150_REG_INT_CABLE_ATTACH_MASK; 1184ed754deSVijai Kumar K if (cable_attach) { 1194ed754deSVijai Kumar K unsigned int port_status; 1204ed754deSVijai Kumar K unsigned int vbus; 1214ed754deSVijai Kumar K 1224ed754deSVijai Kumar K port_status = ((reg_data & 1234ed754deSVijai Kumar K PTN5150_REG_CC_PORT_ATTACHMENT_MASK) >> 1244ed754deSVijai Kumar K PTN5150_REG_CC_PORT_ATTACHMENT_SHIFT); 1254ed754deSVijai Kumar K 1264ed754deSVijai Kumar K switch (port_status) { 1274ed754deSVijai Kumar K case PTN5150_DFP_ATTACHED: 1284ed754deSVijai Kumar K extcon_set_state_sync(info->edev, 1294ed754deSVijai Kumar K EXTCON_USB_HOST, false); 1306aaad58cSKrzysztof Kozlowski gpiod_set_value_cansleep(info->vbus_gpiod, 0); 1314ed754deSVijai Kumar K extcon_set_state_sync(info->edev, EXTCON_USB, 1324ed754deSVijai Kumar K true); 1334ed754deSVijai Kumar K break; 1344ed754deSVijai Kumar K case PTN5150_UFP_ATTACHED: 1354ed754deSVijai Kumar K extcon_set_state_sync(info->edev, EXTCON_USB, 1364ed754deSVijai Kumar K false); 1374ed754deSVijai Kumar K vbus = ((reg_data & 1384ed754deSVijai Kumar K PTN5150_REG_CC_VBUS_DETECTION_MASK) >> 1394ed754deSVijai Kumar K PTN5150_REG_CC_VBUS_DETECTION_SHIFT); 1404ed754deSVijai Kumar K if (vbus) 1416aaad58cSKrzysztof Kozlowski gpiod_set_value_cansleep(info->vbus_gpiod, 0); 1424ed754deSVijai Kumar K else 1436aaad58cSKrzysztof Kozlowski gpiod_set_value_cansleep(info->vbus_gpiod, 1); 1444ed754deSVijai Kumar K 1454ed754deSVijai Kumar K extcon_set_state_sync(info->edev, 1464ed754deSVijai Kumar K EXTCON_USB_HOST, true); 1474ed754deSVijai Kumar K break; 1484ed754deSVijai Kumar K default: 1494ed754deSVijai Kumar K dev_err(info->dev, 1504ed754deSVijai Kumar K "Unknown Port status : %x\n", 1514ed754deSVijai Kumar K port_status); 1524ed754deSVijai Kumar K break; 1534ed754deSVijai Kumar K } 1544ed754deSVijai Kumar K } else { 1554ed754deSVijai Kumar K extcon_set_state_sync(info->edev, 1564ed754deSVijai Kumar K EXTCON_USB_HOST, false); 1574ed754deSVijai Kumar K extcon_set_state_sync(info->edev, 1584ed754deSVijai Kumar K EXTCON_USB, false); 1596aaad58cSKrzysztof Kozlowski gpiod_set_value_cansleep(info->vbus_gpiod, 0); 1604ed754deSVijai Kumar K } 1614ed754deSVijai Kumar K } 1624ed754deSVijai Kumar K 1634ed754deSVijai Kumar K /* Clear interrupt. Read would clear the register */ 1644ed754deSVijai Kumar K ret = regmap_read(info->regmap, PTN5150_REG_INT_REG_STATUS, 1654ed754deSVijai Kumar K &int_status); 1664ed754deSVijai Kumar K if (ret) { 1674ed754deSVijai Kumar K dev_err(info->dev, 1684ed754deSVijai Kumar K "failed to read INT REG STATUS %d\n", ret); 1694ed754deSVijai Kumar K mutex_unlock(&info->mutex); 1704ed754deSVijai Kumar K return; 1714ed754deSVijai Kumar K } 1724ed754deSVijai Kumar K 1734ed754deSVijai Kumar K mutex_unlock(&info->mutex); 1744ed754deSVijai Kumar K } 1754ed754deSVijai Kumar K 1764ed754deSVijai Kumar K 1774ed754deSVijai Kumar K static irqreturn_t ptn5150_irq_handler(int irq, void *data) 1784ed754deSVijai Kumar K { 1794ed754deSVijai Kumar K struct ptn5150_info *info = data; 1804ed754deSVijai Kumar K 1814ed754deSVijai Kumar K schedule_work(&info->irq_work); 1824ed754deSVijai Kumar K 1834ed754deSVijai Kumar K return IRQ_HANDLED; 1844ed754deSVijai Kumar K } 1854ed754deSVijai Kumar K 1864ed754deSVijai Kumar K static int ptn5150_init_dev_type(struct ptn5150_info *info) 1874ed754deSVijai Kumar K { 1884ed754deSVijai Kumar K unsigned int reg_data, vendor_id, version_id; 1894ed754deSVijai Kumar K int ret; 1904ed754deSVijai Kumar K 1914ed754deSVijai Kumar K ret = regmap_read(info->regmap, PTN5150_REG_DEVICE_ID, ®_data); 1924ed754deSVijai Kumar K if (ret) { 1934ed754deSVijai Kumar K dev_err(info->dev, "failed to read DEVICE_ID %d\n", ret); 1944ed754deSVijai Kumar K return -EINVAL; 1954ed754deSVijai Kumar K } 1964ed754deSVijai Kumar K 1974ed754deSVijai Kumar K vendor_id = ((reg_data & PTN5150_REG_DEVICE_ID_VENDOR_MASK) >> 1984ed754deSVijai Kumar K PTN5150_REG_DEVICE_ID_VENDOR_SHIFT); 1994ed754deSVijai Kumar K version_id = ((reg_data & PTN5150_REG_DEVICE_ID_VERSION_MASK) >> 2004ed754deSVijai Kumar K PTN5150_REG_DEVICE_ID_VERSION_SHIFT); 2014ed754deSVijai Kumar K 202fa31f587SKrzysztof Kozlowski dev_dbg(info->dev, "Device type: version: 0x%x, vendor: 0x%x\n", 2034ed754deSVijai Kumar K version_id, vendor_id); 2044ed754deSVijai Kumar K 2054ed754deSVijai Kumar K /* Clear any existing interrupts */ 2064ed754deSVijai Kumar K ret = regmap_read(info->regmap, PTN5150_REG_INT_STATUS, ®_data); 2074ed754deSVijai Kumar K if (ret) { 2084ed754deSVijai Kumar K dev_err(info->dev, 2094ed754deSVijai Kumar K "failed to read PTN5150_REG_INT_STATUS %d\n", 2104ed754deSVijai Kumar K ret); 2114ed754deSVijai Kumar K return -EINVAL; 2124ed754deSVijai Kumar K } 2134ed754deSVijai Kumar K 2144ed754deSVijai Kumar K ret = regmap_read(info->regmap, PTN5150_REG_INT_REG_STATUS, ®_data); 2154ed754deSVijai Kumar K if (ret) { 2164ed754deSVijai Kumar K dev_err(info->dev, 2174ed754deSVijai Kumar K "failed to read PTN5150_REG_INT_REG_STATUS %d\n", ret); 2184ed754deSVijai Kumar K return -EINVAL; 2194ed754deSVijai Kumar K } 2204ed754deSVijai Kumar K 2214ed754deSVijai Kumar K return 0; 2224ed754deSVijai Kumar K } 2234ed754deSVijai Kumar K 2244ed754deSVijai Kumar K static int ptn5150_i2c_probe(struct i2c_client *i2c, 2254ed754deSVijai Kumar K const struct i2c_device_id *id) 2264ed754deSVijai Kumar K { 2274ed754deSVijai Kumar K struct device *dev = &i2c->dev; 2284ed754deSVijai Kumar K struct device_node *np = i2c->dev.of_node; 2294ed754deSVijai Kumar K struct ptn5150_info *info; 2304ed754deSVijai Kumar K int ret; 2314ed754deSVijai Kumar K 2324ed754deSVijai Kumar K if (!np) 2334ed754deSVijai Kumar K return -EINVAL; 2344ed754deSVijai Kumar K 2354ed754deSVijai Kumar K info = devm_kzalloc(&i2c->dev, sizeof(*info), GFP_KERNEL); 2364ed754deSVijai Kumar K if (!info) 2374ed754deSVijai Kumar K return -ENOMEM; 2384ed754deSVijai Kumar K i2c_set_clientdata(i2c, info); 2394ed754deSVijai Kumar K 2404ed754deSVijai Kumar K info->dev = &i2c->dev; 2414ed754deSVijai Kumar K info->i2c = i2c; 242e095882eSKrzysztof Kozlowski info->vbus_gpiod = devm_gpiod_get(&i2c->dev, "vbus", GPIOD_OUT_LOW); 2433dfed895SWei Yongjun if (IS_ERR(info->vbus_gpiod)) { 2444ed754deSVijai Kumar K dev_err(dev, "failed to get VBUS GPIO\n"); 2453dfed895SWei Yongjun return PTR_ERR(info->vbus_gpiod); 2464ed754deSVijai Kumar K } 2474ed754deSVijai Kumar K 2484ed754deSVijai Kumar K mutex_init(&info->mutex); 2494ed754deSVijai Kumar K 2504ed754deSVijai Kumar K INIT_WORK(&info->irq_work, ptn5150_irq_work); 2514ed754deSVijai Kumar K 2524ed754deSVijai Kumar K info->regmap = devm_regmap_init_i2c(i2c, &ptn5150_regmap_config); 2534ed754deSVijai Kumar K if (IS_ERR(info->regmap)) { 2544ed754deSVijai Kumar K ret = PTR_ERR(info->regmap); 2554ed754deSVijai Kumar K dev_err(info->dev, "failed to allocate register map: %d\n", 2564ed754deSVijai Kumar K ret); 2574ed754deSVijai Kumar K return ret; 2584ed754deSVijai Kumar K } 2594ed754deSVijai Kumar K 26045ce36f5SKrzysztof Kozlowski if (i2c->irq > 0) { 26145ce36f5SKrzysztof Kozlowski info->irq = i2c->irq; 26245ce36f5SKrzysztof Kozlowski } else { 26345ce36f5SKrzysztof Kozlowski info->int_gpiod = devm_gpiod_get(&i2c->dev, "int", GPIOD_IN); 26445ce36f5SKrzysztof Kozlowski if (IS_ERR(info->int_gpiod)) { 26545ce36f5SKrzysztof Kozlowski dev_err(dev, "failed to get INT GPIO\n"); 26645ce36f5SKrzysztof Kozlowski return PTR_ERR(info->int_gpiod); 26745ce36f5SKrzysztof Kozlowski } 26845ce36f5SKrzysztof Kozlowski 2694ed754deSVijai Kumar K info->irq = gpiod_to_irq(info->int_gpiod); 2704ed754deSVijai Kumar K if (info->irq < 0) { 2714ed754deSVijai Kumar K dev_err(dev, "failed to get INTB IRQ\n"); 2724ed754deSVijai Kumar K return info->irq; 2734ed754deSVijai Kumar K } 27445ce36f5SKrzysztof Kozlowski } 2754ed754deSVijai Kumar K 2764ed754deSVijai Kumar K ret = devm_request_threaded_irq(dev, info->irq, NULL, 2774ed754deSVijai Kumar K ptn5150_irq_handler, 2784ed754deSVijai Kumar K IRQF_TRIGGER_FALLING | 2794ed754deSVijai Kumar K IRQF_ONESHOT, 2804ed754deSVijai Kumar K i2c->name, info); 2814ed754deSVijai Kumar K if (ret < 0) { 2824ed754deSVijai Kumar K dev_err(dev, "failed to request handler for INTB IRQ\n"); 2834ed754deSVijai Kumar K return ret; 2844ed754deSVijai Kumar K } 2854ed754deSVijai Kumar K 2864ed754deSVijai Kumar K /* Allocate extcon device */ 2874ed754deSVijai Kumar K info->edev = devm_extcon_dev_allocate(info->dev, ptn5150_extcon_cable); 2884ed754deSVijai Kumar K if (IS_ERR(info->edev)) { 2894ed754deSVijai Kumar K dev_err(info->dev, "failed to allocate memory for extcon\n"); 2904ed754deSVijai Kumar K return -ENOMEM; 2914ed754deSVijai Kumar K } 2924ed754deSVijai Kumar K 2934ed754deSVijai Kumar K /* Register extcon device */ 2944ed754deSVijai Kumar K ret = devm_extcon_dev_register(info->dev, info->edev); 2954ed754deSVijai Kumar K if (ret) { 2964ed754deSVijai Kumar K dev_err(info->dev, "failed to register extcon device\n"); 2974ed754deSVijai Kumar K return ret; 2984ed754deSVijai Kumar K } 2994ed754deSVijai Kumar K 3004ed754deSVijai Kumar K /* Initialize PTN5150 device and print vendor id and version id */ 3014ed754deSVijai Kumar K ret = ptn5150_init_dev_type(info); 3024ed754deSVijai Kumar K if (ret) 3034ed754deSVijai Kumar K return -EINVAL; 3044ed754deSVijai Kumar K 3054ed754deSVijai Kumar K return 0; 3064ed754deSVijai Kumar K } 3074ed754deSVijai Kumar K 3084ed754deSVijai Kumar K static const struct of_device_id ptn5150_dt_match[] = { 3094ed754deSVijai Kumar K { .compatible = "nxp,ptn5150" }, 3104ed754deSVijai Kumar K { }, 3114ed754deSVijai Kumar K }; 3124ed754deSVijai Kumar K MODULE_DEVICE_TABLE(of, ptn5150_dt_match); 3134ed754deSVijai Kumar K 3144ed754deSVijai Kumar K static const struct i2c_device_id ptn5150_i2c_id[] = { 3154ed754deSVijai Kumar K { "ptn5150", 0 }, 3164ed754deSVijai Kumar K { } 3174ed754deSVijai Kumar K }; 3184ed754deSVijai Kumar K MODULE_DEVICE_TABLE(i2c, ptn5150_i2c_id); 3194ed754deSVijai Kumar K 3204ed754deSVijai Kumar K static struct i2c_driver ptn5150_i2c_driver = { 3214ed754deSVijai Kumar K .driver = { 3224ed754deSVijai Kumar K .name = "ptn5150", 3234ed754deSVijai Kumar K .of_match_table = ptn5150_dt_match, 3244ed754deSVijai Kumar K }, 3254ed754deSVijai Kumar K .probe = ptn5150_i2c_probe, 3264ed754deSVijai Kumar K .id_table = ptn5150_i2c_id, 3274ed754deSVijai Kumar K }; 3284ed754deSVijai Kumar K 3294ed754deSVijai Kumar K static int __init ptn5150_i2c_init(void) 3304ed754deSVijai Kumar K { 3314ed754deSVijai Kumar K return i2c_add_driver(&ptn5150_i2c_driver); 3324ed754deSVijai Kumar K } 3334ed754deSVijai Kumar K subsys_initcall(ptn5150_i2c_init); 3344ed754deSVijai Kumar K 3354ed754deSVijai Kumar K MODULE_DESCRIPTION("NXP PTN5150 CC logic Extcon driver"); 3364ed754deSVijai Kumar K MODULE_AUTHOR("Vijai Kumar K <vijaikumar.kanagarajan@gmail.com>"); 3374ed754deSVijai Kumar K MODULE_LICENSE("GPL v2"); 338