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, &reg_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, &reg_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, &reg_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, &reg_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