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