xref: /openbmc/linux/drivers/extcon/extcon-gpio.c (revision f94a5bec)
19c92ab61SThomas Gleixner // SPDX-License-Identifier: GPL-2.0-only
20ea62503SMyungJoo Ham /*
36ba12997SChanwoo Choi  * extcon_gpio.c - Single-state GPIO extcon driver based on extcon class
40ea62503SMyungJoo Ham  *
50ea62503SMyungJoo Ham  * Copyright (C) 2008 Google, Inc.
60ea62503SMyungJoo Ham  * Author: Mike Lockwood <lockwood@android.com>
70ea62503SMyungJoo Ham  *
80ea62503SMyungJoo Ham  * Modified by MyungJoo Ham <myungjoo.ham@samsung.com> to support extcon
90ea62503SMyungJoo Ham  * (originally switch class is supported)
100ea62503SMyungJoo Ham  */
110ea62503SMyungJoo Ham 
12*f94a5becSMatti Vaittinen #include <linux/devm-helpers.h>
13176aa360SChanwoo Choi #include <linux/extcon-provider.h>
14de992acbSChanwoo Choi #include <linux/gpio/consumer.h>
150ea62503SMyungJoo Ham #include <linux/init.h>
160ea62503SMyungJoo Ham #include <linux/interrupt.h>
1762364357SGeorge Cherian #include <linux/kernel.h>
1862364357SGeorge Cherian #include <linux/module.h>
190ea62503SMyungJoo Ham #include <linux/platform_device.h>
200ea62503SMyungJoo Ham #include <linux/slab.h>
210ea62503SMyungJoo Ham #include <linux/workqueue.h>
220ea62503SMyungJoo Ham 
2366afdedfSLinus Walleij /**
24a62300d9SLinus Walleij  * struct gpio_extcon_data - A simple GPIO-controlled extcon device state container.
25a62300d9SLinus Walleij  * @edev:		Extcon device.
26a62300d9SLinus Walleij  * @work:		Work fired by the interrupt.
27a62300d9SLinus Walleij  * @debounce_jiffies:	Number of jiffies to wait for the GPIO to stabilize, from the debounce
28a62300d9SLinus Walleij  *			value.
29d368e7deSLinus Walleij  * @gpiod:		GPIO descriptor for this external connector.
3066afdedfSLinus Walleij  * @extcon_id:		The unique id of specific external connector.
3166afdedfSLinus Walleij  * @debounce:		Debounce time for GPIO IRQ in ms.
3266afdedfSLinus Walleij  * @check_on_resume:	Boolean describing whether to check the state of gpio
3366afdedfSLinus Walleij  *			while resuming from sleep.
3466afdedfSLinus Walleij  */
35a62300d9SLinus Walleij struct gpio_extcon_data {
36a62300d9SLinus Walleij 	struct extcon_dev *edev;
37a62300d9SLinus Walleij 	struct delayed_work work;
38a62300d9SLinus Walleij 	unsigned long debounce_jiffies;
39d368e7deSLinus Walleij 	struct gpio_desc *gpiod;
4066afdedfSLinus Walleij 	unsigned int extcon_id;
4166afdedfSLinus Walleij 	unsigned long debounce;
4266afdedfSLinus Walleij 	bool check_on_resume;
4366afdedfSLinus Walleij };
4466afdedfSLinus Walleij 
gpio_extcon_work(struct work_struct * work)450ea62503SMyungJoo Ham static void gpio_extcon_work(struct work_struct *work)
460ea62503SMyungJoo Ham {
470ea62503SMyungJoo Ham 	int state;
480ea62503SMyungJoo Ham 	struct gpio_extcon_data	*data =
490ea62503SMyungJoo Ham 		container_of(to_delayed_work(work), struct gpio_extcon_data,
500ea62503SMyungJoo Ham 			     work);
510ea62503SMyungJoo Ham 
52d368e7deSLinus Walleij 	state = gpiod_get_value_cansleep(data->gpiod);
53a62300d9SLinus Walleij 	extcon_set_state_sync(data->edev, data->extcon_id, state);
540ea62503SMyungJoo Ham }
550ea62503SMyungJoo Ham 
gpio_irq_handler(int irq,void * dev_id)560ea62503SMyungJoo Ham static irqreturn_t gpio_irq_handler(int irq, void *dev_id)
570ea62503SMyungJoo Ham {
5860f9b9e6SChanwoo Choi 	struct gpio_extcon_data *data = dev_id;
590ea62503SMyungJoo Ham 
6060f9b9e6SChanwoo Choi 	queue_delayed_work(system_power_efficient_wq, &data->work,
6160f9b9e6SChanwoo Choi 			      data->debounce_jiffies);
620ea62503SMyungJoo Ham 	return IRQ_HANDLED;
630ea62503SMyungJoo Ham }
640ea62503SMyungJoo Ham 
gpio_extcon_probe(struct platform_device * pdev)6544f34fd4SBill Pemberton static int gpio_extcon_probe(struct platform_device *pdev)
660ea62503SMyungJoo Ham {
6760f9b9e6SChanwoo Choi 	struct gpio_extcon_data *data;
68d368e7deSLinus Walleij 	struct device *dev = &pdev->dev;
698bc4810bSLinus Walleij 	unsigned long irq_flags;
708bc4810bSLinus Walleij 	int irq;
711073514bSGuenter Roeck 	int ret;
720ea62503SMyungJoo Ham 
73d368e7deSLinus Walleij 	data = devm_kzalloc(dev, sizeof(struct gpio_extcon_data), GFP_KERNEL);
7460f9b9e6SChanwoo Choi 	if (!data)
750ea62503SMyungJoo Ham 		return -ENOMEM;
76a62300d9SLinus Walleij 
77a62300d9SLinus Walleij 	/*
78a62300d9SLinus Walleij 	 * FIXME: extcon_id represents the unique identifier of external
79a62300d9SLinus Walleij 	 * connectors such as EXTCON_USB, EXTCON_DISP_HDMI and so on. extcon_id
80a62300d9SLinus Walleij 	 * is necessary to register the extcon device. But, it's not yet
81a62300d9SLinus Walleij 	 * developed to get the extcon id from device-tree or others.
82a62300d9SLinus Walleij 	 * On later, it have to be solved.
83a62300d9SLinus Walleij 	 */
848bc4810bSLinus Walleij 	if (data->extcon_id > EXTCON_NONE)
85a62300d9SLinus Walleij 		return -EINVAL;
860ea62503SMyungJoo Ham 
87d368e7deSLinus Walleij 	data->gpiod = devm_gpiod_get(dev, "extcon", GPIOD_IN);
88d368e7deSLinus Walleij 	if (IS_ERR(data->gpiod))
89d368e7deSLinus Walleij 		return PTR_ERR(data->gpiod);
908bc4810bSLinus Walleij 	irq = gpiod_to_irq(data->gpiod);
918bc4810bSLinus Walleij 	if (irq <= 0)
928bc4810bSLinus Walleij 		return irq;
938bc4810bSLinus Walleij 
948bc4810bSLinus Walleij 	/*
958bc4810bSLinus Walleij 	 * It is unlikely that this is an acknowledged interrupt that goes
968bc4810bSLinus Walleij 	 * away after handling, what we are looking for are falling edges
978bc4810bSLinus Walleij 	 * if the signal is active low, and rising edges if the signal is
988bc4810bSLinus Walleij 	 * active high.
998bc4810bSLinus Walleij 	 */
1008bc4810bSLinus Walleij 	if (gpiod_is_active_low(data->gpiod))
1018bc4810bSLinus Walleij 		irq_flags = IRQF_TRIGGER_FALLING;
1028bc4810bSLinus Walleij 	else
1038bc4810bSLinus Walleij 		irq_flags = IRQF_TRIGGER_RISING;
104de992acbSChanwoo Choi 
105de992acbSChanwoo Choi 	/* Allocate the memory of extcon devie and register extcon device */
106d368e7deSLinus Walleij 	data->edev = devm_extcon_dev_allocate(dev, &data->extcon_id);
10760f9b9e6SChanwoo Choi 	if (IS_ERR(data->edev)) {
108d368e7deSLinus Walleij 		dev_err(dev, "failed to allocate extcon device\n");
10960cd62d4SChanwoo Choi 		return -ENOMEM;
11060cd62d4SChanwoo Choi 	}
1110ea62503SMyungJoo Ham 
112d368e7deSLinus Walleij 	ret = devm_extcon_dev_register(dev, data->edev);
1130ea62503SMyungJoo Ham 	if (ret < 0)
1140ea62503SMyungJoo Ham 		return ret;
1150ea62503SMyungJoo Ham 
116*f94a5becSMatti Vaittinen 	ret = devm_delayed_work_autocancel(dev, &data->work, gpio_extcon_work);
117*f94a5becSMatti Vaittinen 	if (ret)
118*f94a5becSMatti Vaittinen 		return ret;
1190ea62503SMyungJoo Ham 
120de992acbSChanwoo Choi 	/*
121b51b3870SMoritz Fischer 	 * Request the interrupt of gpio to detect whether external connector
122de992acbSChanwoo Choi 	 * is attached or detached.
123de992acbSChanwoo Choi 	 */
1248bc4810bSLinus Walleij 	ret = devm_request_any_context_irq(dev, irq,
1258bc4810bSLinus Walleij 					gpio_irq_handler, irq_flags,
12660f9b9e6SChanwoo Choi 					pdev->name, data);
1270ea62503SMyungJoo Ham 	if (ret < 0)
128d92c2f12SSangjung Woo 		return ret;
1290ea62503SMyungJoo Ham 
13060f9b9e6SChanwoo Choi 	platform_set_drvdata(pdev, data);
1310ea62503SMyungJoo Ham 	/* Perform initial detection */
13260f9b9e6SChanwoo Choi 	gpio_extcon_work(&data->work.work);
1330ea62503SMyungJoo Ham 
1340ea62503SMyungJoo Ham 	return 0;
1350ea62503SMyungJoo Ham }
1360ea62503SMyungJoo Ham 
1376544dfa5SRongjun Ying #ifdef CONFIG_PM_SLEEP
gpio_extcon_resume(struct device * dev)1386544dfa5SRongjun Ying static int gpio_extcon_resume(struct device *dev)
1396544dfa5SRongjun Ying {
14060f9b9e6SChanwoo Choi 	struct gpio_extcon_data *data;
1416544dfa5SRongjun Ying 
14260f9b9e6SChanwoo Choi 	data = dev_get_drvdata(dev);
143a62300d9SLinus Walleij 	if (data->check_on_resume)
1446544dfa5SRongjun Ying 		queue_delayed_work(system_power_efficient_wq,
14560f9b9e6SChanwoo Choi 			&data->work, data->debounce_jiffies);
1466544dfa5SRongjun Ying 
1476544dfa5SRongjun Ying 	return 0;
1486544dfa5SRongjun Ying }
1496544dfa5SRongjun Ying #endif
1506544dfa5SRongjun Ying 
1513cc731d9SJingoo Han static SIMPLE_DEV_PM_OPS(gpio_extcon_pm_ops, NULL, gpio_extcon_resume);
1526544dfa5SRongjun Ying 
1530ea62503SMyungJoo Ham static struct platform_driver gpio_extcon_driver = {
1540ea62503SMyungJoo Ham 	.probe		= gpio_extcon_probe,
1550ea62503SMyungJoo Ham 	.driver		= {
1560ea62503SMyungJoo Ham 		.name	= "extcon-gpio",
1576544dfa5SRongjun Ying 		.pm	= &gpio_extcon_pm_ops,
1580ea62503SMyungJoo Ham 	},
1590ea62503SMyungJoo Ham };
1600ea62503SMyungJoo Ham 
1610ea62503SMyungJoo Ham module_platform_driver(gpio_extcon_driver);
1620ea62503SMyungJoo Ham 
1630ea62503SMyungJoo Ham MODULE_AUTHOR("Mike Lockwood <lockwood@android.com>");
1640ea62503SMyungJoo Ham MODULE_DESCRIPTION("GPIO extcon driver");
1650ea62503SMyungJoo Ham MODULE_LICENSE("GPL");
166