xref: /openbmc/linux/drivers/extcon/extcon-qcom-spmi-misc.c (revision 2612e3bbc0386368a850140a6c9b990cd496a5ec)
11802d0beSThomas Gleixner // SPDX-License-Identifier: GPL-2.0-only
238085c98SStephen Boyd /**
338085c98SStephen Boyd  * extcon-qcom-spmi-misc.c - Qualcomm USB extcon driver to support USB ID
47b1222b2SAnirudh Ghayal  *			and VBUS detection based on extcon-usb-gpio.c.
538085c98SStephen Boyd  *
638085c98SStephen Boyd  * Copyright (C) 2016 Linaro, Ltd.
738085c98SStephen Boyd  * Stephen Boyd <stephen.boyd@linaro.org>
838085c98SStephen Boyd  */
938085c98SStephen Boyd 
10f94a5becSMatti Vaittinen #include <linux/devm-helpers.h>
11176aa360SChanwoo Choi #include <linux/extcon-provider.h>
1238085c98SStephen Boyd #include <linux/init.h>
1338085c98SStephen Boyd #include <linux/interrupt.h>
1438085c98SStephen Boyd #include <linux/kernel.h>
1538085c98SStephen Boyd #include <linux/module.h>
16ac316725SRandy Dunlap #include <linux/mod_devicetable.h>
1738085c98SStephen Boyd #include <linux/platform_device.h>
1838085c98SStephen Boyd #include <linux/slab.h>
1938085c98SStephen Boyd #include <linux/workqueue.h>
2038085c98SStephen Boyd 
2138085c98SStephen Boyd #define USB_ID_DEBOUNCE_MS	5	/* ms */
2238085c98SStephen Boyd 
2338085c98SStephen Boyd struct qcom_usb_extcon_info {
2438085c98SStephen Boyd 	struct extcon_dev *edev;
257b1222b2SAnirudh Ghayal 	int id_irq;
267b1222b2SAnirudh Ghayal 	int vbus_irq;
2738085c98SStephen Boyd 	struct delayed_work wq_detcable;
2838085c98SStephen Boyd 	unsigned long debounce_jiffies;
2938085c98SStephen Boyd };
3038085c98SStephen Boyd 
3138085c98SStephen Boyd static const unsigned int qcom_usb_extcon_cable[] = {
327b1222b2SAnirudh Ghayal 	EXTCON_USB,
3338085c98SStephen Boyd 	EXTCON_USB_HOST,
3438085c98SStephen Boyd 	EXTCON_NONE,
3538085c98SStephen Boyd };
3638085c98SStephen Boyd 
qcom_usb_extcon_detect_cable(struct work_struct * work)3738085c98SStephen Boyd static void qcom_usb_extcon_detect_cable(struct work_struct *work)
3838085c98SStephen Boyd {
397b1222b2SAnirudh Ghayal 	bool state = false;
4038085c98SStephen Boyd 	int ret;
417b1222b2SAnirudh Ghayal 	union extcon_property_value val;
4238085c98SStephen Boyd 	struct qcom_usb_extcon_info *info = container_of(to_delayed_work(work),
4338085c98SStephen Boyd 						    struct qcom_usb_extcon_info,
4438085c98SStephen Boyd 						    wq_detcable);
4538085c98SStephen Boyd 
467b1222b2SAnirudh Ghayal 	if (info->id_irq > 0) {
4738085c98SStephen Boyd 		/* check ID and update cable state */
487b1222b2SAnirudh Ghayal 		ret = irq_get_irqchip_state(info->id_irq,
497b1222b2SAnirudh Ghayal 				IRQCHIP_STATE_LINE_LEVEL, &state);
5038085c98SStephen Boyd 		if (ret)
5138085c98SStephen Boyd 			return;
5238085c98SStephen Boyd 
537b1222b2SAnirudh Ghayal 		if (!state) {
547b1222b2SAnirudh Ghayal 			val.intval = true;
557b1222b2SAnirudh Ghayal 			extcon_set_property(info->edev, EXTCON_USB_HOST,
567b1222b2SAnirudh Ghayal 						EXTCON_PROP_USB_SS, val);
577b1222b2SAnirudh Ghayal 		}
587b1222b2SAnirudh Ghayal 		extcon_set_state_sync(info->edev, EXTCON_USB_HOST, !state);
597b1222b2SAnirudh Ghayal 	}
607b1222b2SAnirudh Ghayal 
617b1222b2SAnirudh Ghayal 	if (info->vbus_irq > 0) {
627b1222b2SAnirudh Ghayal 		/* check VBUS and update cable state */
637b1222b2SAnirudh Ghayal 		ret = irq_get_irqchip_state(info->vbus_irq,
647b1222b2SAnirudh Ghayal 				IRQCHIP_STATE_LINE_LEVEL, &state);
657b1222b2SAnirudh Ghayal 		if (ret)
667b1222b2SAnirudh Ghayal 			return;
677b1222b2SAnirudh Ghayal 
687b1222b2SAnirudh Ghayal 		if (state) {
697b1222b2SAnirudh Ghayal 			val.intval = true;
707b1222b2SAnirudh Ghayal 			extcon_set_property(info->edev, EXTCON_USB,
717b1222b2SAnirudh Ghayal 						EXTCON_PROP_USB_SS, val);
727b1222b2SAnirudh Ghayal 		}
737b1222b2SAnirudh Ghayal 		extcon_set_state_sync(info->edev, EXTCON_USB, state);
747b1222b2SAnirudh Ghayal 	}
7538085c98SStephen Boyd }
7638085c98SStephen Boyd 
qcom_usb_irq_handler(int irq,void * dev_id)7738085c98SStephen Boyd static irqreturn_t qcom_usb_irq_handler(int irq, void *dev_id)
7838085c98SStephen Boyd {
7938085c98SStephen Boyd 	struct qcom_usb_extcon_info *info = dev_id;
8038085c98SStephen Boyd 
8138085c98SStephen Boyd 	queue_delayed_work(system_power_efficient_wq, &info->wq_detcable,
8238085c98SStephen Boyd 			   info->debounce_jiffies);
8338085c98SStephen Boyd 
8438085c98SStephen Boyd 	return IRQ_HANDLED;
8538085c98SStephen Boyd }
8638085c98SStephen Boyd 
qcom_usb_extcon_probe(struct platform_device * pdev)8738085c98SStephen Boyd static int qcom_usb_extcon_probe(struct platform_device *pdev)
8838085c98SStephen Boyd {
8938085c98SStephen Boyd 	struct device *dev = &pdev->dev;
9038085c98SStephen Boyd 	struct qcom_usb_extcon_info *info;
9138085c98SStephen Boyd 	int ret;
9238085c98SStephen Boyd 
9338085c98SStephen Boyd 	info = devm_kzalloc(dev, sizeof(*info), GFP_KERNEL);
9438085c98SStephen Boyd 	if (!info)
9538085c98SStephen Boyd 		return -ENOMEM;
9638085c98SStephen Boyd 
9738085c98SStephen Boyd 	info->edev = devm_extcon_dev_allocate(dev, qcom_usb_extcon_cable);
9838085c98SStephen Boyd 	if (IS_ERR(info->edev)) {
9938085c98SStephen Boyd 		dev_err(dev, "failed to allocate extcon device\n");
10038085c98SStephen Boyd 		return -ENOMEM;
10138085c98SStephen Boyd 	}
10238085c98SStephen Boyd 
10338085c98SStephen Boyd 	ret = devm_extcon_dev_register(dev, info->edev);
10438085c98SStephen Boyd 	if (ret < 0) {
10538085c98SStephen Boyd 		dev_err(dev, "failed to register extcon device\n");
10638085c98SStephen Boyd 		return ret;
10738085c98SStephen Boyd 	}
10838085c98SStephen Boyd 
1097b1222b2SAnirudh Ghayal 	ret = extcon_set_property_capability(info->edev,
1107b1222b2SAnirudh Ghayal 			EXTCON_USB, EXTCON_PROP_USB_SS);
1117b1222b2SAnirudh Ghayal 	ret |= extcon_set_property_capability(info->edev,
1127b1222b2SAnirudh Ghayal 			EXTCON_USB_HOST, EXTCON_PROP_USB_SS);
1137b1222b2SAnirudh Ghayal 	if (ret) {
1147b1222b2SAnirudh Ghayal 		dev_err(dev, "failed to register extcon props rc=%d\n",
1157b1222b2SAnirudh Ghayal 						ret);
1167b1222b2SAnirudh Ghayal 		return ret;
1177b1222b2SAnirudh Ghayal 	}
1187b1222b2SAnirudh Ghayal 
11938085c98SStephen Boyd 	info->debounce_jiffies = msecs_to_jiffies(USB_ID_DEBOUNCE_MS);
120f94a5becSMatti Vaittinen 
121f94a5becSMatti Vaittinen 	ret = devm_delayed_work_autocancel(dev, &info->wq_detcable,
122f94a5becSMatti Vaittinen 					   qcom_usb_extcon_detect_cable);
123f94a5becSMatti Vaittinen 	if (ret)
124f94a5becSMatti Vaittinen 		return ret;
12538085c98SStephen Boyd 
126*fe551bc9SBryan O'Donoghue 	info->id_irq = platform_get_irq_byname_optional(pdev, "usb_id");
1277b1222b2SAnirudh Ghayal 	if (info->id_irq > 0) {
1287b1222b2SAnirudh Ghayal 		ret = devm_request_threaded_irq(dev, info->id_irq, NULL,
12938085c98SStephen Boyd 					qcom_usb_irq_handler,
13038085c98SStephen Boyd 					IRQF_TRIGGER_RISING |
13138085c98SStephen Boyd 					IRQF_TRIGGER_FALLING | IRQF_ONESHOT,
13238085c98SStephen Boyd 					pdev->name, info);
13338085c98SStephen Boyd 		if (ret < 0) {
13438085c98SStephen Boyd 			dev_err(dev, "failed to request handler for ID IRQ\n");
13538085c98SStephen Boyd 			return ret;
13638085c98SStephen Boyd 		}
1377b1222b2SAnirudh Ghayal 	}
1387b1222b2SAnirudh Ghayal 
139*fe551bc9SBryan O'Donoghue 	info->vbus_irq = platform_get_irq_byname_optional(pdev, "usb_vbus");
1407b1222b2SAnirudh Ghayal 	if (info->vbus_irq > 0) {
1417b1222b2SAnirudh Ghayal 		ret = devm_request_threaded_irq(dev, info->vbus_irq, NULL,
1427b1222b2SAnirudh Ghayal 					qcom_usb_irq_handler,
1437b1222b2SAnirudh Ghayal 					IRQF_TRIGGER_RISING |
1447b1222b2SAnirudh Ghayal 					IRQF_TRIGGER_FALLING | IRQF_ONESHOT,
1457b1222b2SAnirudh Ghayal 					pdev->name, info);
1467b1222b2SAnirudh Ghayal 		if (ret < 0) {
1477b1222b2SAnirudh Ghayal 			dev_err(dev, "failed to request handler for VBUS IRQ\n");
1487b1222b2SAnirudh Ghayal 			return ret;
1497b1222b2SAnirudh Ghayal 		}
1507b1222b2SAnirudh Ghayal 	}
1517b1222b2SAnirudh Ghayal 
1527b1222b2SAnirudh Ghayal 	if (info->id_irq < 0 && info->vbus_irq < 0) {
1537b1222b2SAnirudh Ghayal 		dev_err(dev, "ID and VBUS IRQ not found\n");
1547b1222b2SAnirudh Ghayal 		return -EINVAL;
1557b1222b2SAnirudh Ghayal 	}
15638085c98SStephen Boyd 
15738085c98SStephen Boyd 	platform_set_drvdata(pdev, info);
15838085c98SStephen Boyd 	device_init_wakeup(dev, 1);
15938085c98SStephen Boyd 
16038085c98SStephen Boyd 	/* Perform initial detection */
16138085c98SStephen Boyd 	qcom_usb_extcon_detect_cable(&info->wq_detcable.work);
16238085c98SStephen Boyd 
16338085c98SStephen Boyd 	return 0;
16438085c98SStephen Boyd }
16538085c98SStephen Boyd 
16638085c98SStephen Boyd #ifdef CONFIG_PM_SLEEP
qcom_usb_extcon_suspend(struct device * dev)16738085c98SStephen Boyd static int qcom_usb_extcon_suspend(struct device *dev)
16838085c98SStephen Boyd {
16938085c98SStephen Boyd 	struct qcom_usb_extcon_info *info = dev_get_drvdata(dev);
17038085c98SStephen Boyd 	int ret = 0;
17138085c98SStephen Boyd 
1727b1222b2SAnirudh Ghayal 	if (device_may_wakeup(dev)) {
1737b1222b2SAnirudh Ghayal 		if (info->id_irq > 0)
1747b1222b2SAnirudh Ghayal 			ret = enable_irq_wake(info->id_irq);
1757b1222b2SAnirudh Ghayal 		if (info->vbus_irq > 0)
1767b1222b2SAnirudh Ghayal 			ret = enable_irq_wake(info->vbus_irq);
1777b1222b2SAnirudh Ghayal 	}
17838085c98SStephen Boyd 
17938085c98SStephen Boyd 	return ret;
18038085c98SStephen Boyd }
18138085c98SStephen Boyd 
qcom_usb_extcon_resume(struct device * dev)18238085c98SStephen Boyd static int qcom_usb_extcon_resume(struct device *dev)
18338085c98SStephen Boyd {
18438085c98SStephen Boyd 	struct qcom_usb_extcon_info *info = dev_get_drvdata(dev);
18538085c98SStephen Boyd 	int ret = 0;
18638085c98SStephen Boyd 
1877b1222b2SAnirudh Ghayal 	if (device_may_wakeup(dev)) {
1887b1222b2SAnirudh Ghayal 		if (info->id_irq > 0)
1897b1222b2SAnirudh Ghayal 			ret = disable_irq_wake(info->id_irq);
1907b1222b2SAnirudh Ghayal 		if (info->vbus_irq > 0)
1917b1222b2SAnirudh Ghayal 			ret = disable_irq_wake(info->vbus_irq);
1927b1222b2SAnirudh Ghayal 	}
19338085c98SStephen Boyd 
19438085c98SStephen Boyd 	return ret;
19538085c98SStephen Boyd }
19638085c98SStephen Boyd #endif
19738085c98SStephen Boyd 
19838085c98SStephen Boyd static SIMPLE_DEV_PM_OPS(qcom_usb_extcon_pm_ops,
19938085c98SStephen Boyd 			 qcom_usb_extcon_suspend, qcom_usb_extcon_resume);
20038085c98SStephen Boyd 
20138085c98SStephen Boyd static const struct of_device_id qcom_usb_extcon_dt_match[] = {
20238085c98SStephen Boyd 	{ .compatible = "qcom,pm8941-misc", },
20338085c98SStephen Boyd 	{ }
20438085c98SStephen Boyd };
20538085c98SStephen Boyd MODULE_DEVICE_TABLE(of, qcom_usb_extcon_dt_match);
20638085c98SStephen Boyd 
20738085c98SStephen Boyd static struct platform_driver qcom_usb_extcon_driver = {
20838085c98SStephen Boyd 	.probe		= qcom_usb_extcon_probe,
20938085c98SStephen Boyd 	.driver		= {
21038085c98SStephen Boyd 		.name	= "extcon-pm8941-misc",
21138085c98SStephen Boyd 		.pm	= &qcom_usb_extcon_pm_ops,
21238085c98SStephen Boyd 		.of_match_table = qcom_usb_extcon_dt_match,
21338085c98SStephen Boyd 	},
21438085c98SStephen Boyd };
21538085c98SStephen Boyd module_platform_driver(qcom_usb_extcon_driver);
21638085c98SStephen Boyd 
21738085c98SStephen Boyd MODULE_DESCRIPTION("QCOM USB ID extcon driver");
21838085c98SStephen Boyd MODULE_AUTHOR("Stephen Boyd <stephen.boyd@linaro.org>");
21938085c98SStephen Boyd MODULE_LICENSE("GPL v2");
220