mirror of
https://github.com/edk2-porting/linux-next.git
synced 2024-12-29 23:53:55 +08:00
d368e7de5e
Since we are not getting the GPIO from any platform data and global GPIO numberspace, we simply get the named "extcon" GPIO directly from the device. Cut away "active low" since GPIO descriptors already know if the line is active high or low. Simplify a bit with a struct device *dev helper variable in probe() and cut the complex init() function. Signed-off-by: Linus Walleij <linus.walleij@linaro.org> Signed-off-by: Chanwoo Choi <cw00.choi@samsung.com>
172 lines
4.7 KiB
C
172 lines
4.7 KiB
C
/*
|
|
* extcon_gpio.c - Single-state GPIO extcon driver based on extcon class
|
|
*
|
|
* Copyright (C) 2008 Google, Inc.
|
|
* Author: Mike Lockwood <lockwood@android.com>
|
|
*
|
|
* Modified by MyungJoo Ham <myungjoo.ham@samsung.com> to support extcon
|
|
* (originally switch class is supported)
|
|
*
|
|
* This software is licensed under the terms of the GNU General Public
|
|
* License version 2, as published by the Free Software Foundation, and
|
|
* may be copied, distributed, and modified under those terms.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*/
|
|
|
|
#include <linux/extcon-provider.h>
|
|
#include <linux/gpio/consumer.h>
|
|
#include <linux/init.h>
|
|
#include <linux/interrupt.h>
|
|
#include <linux/kernel.h>
|
|
#include <linux/module.h>
|
|
#include <linux/platform_device.h>
|
|
#include <linux/slab.h>
|
|
#include <linux/workqueue.h>
|
|
|
|
/**
|
|
* struct gpio_extcon_data - A simple GPIO-controlled extcon device state container.
|
|
* @edev: Extcon device.
|
|
* @irq: Interrupt line for the external connector.
|
|
* @work: Work fired by the interrupt.
|
|
* @debounce_jiffies: Number of jiffies to wait for the GPIO to stabilize, from the debounce
|
|
* value.
|
|
* @gpiod: GPIO descriptor for this external connector.
|
|
* @extcon_id: The unique id of specific external connector.
|
|
* @debounce: Debounce time for GPIO IRQ in ms.
|
|
* @irq_flags: IRQ Flags (e.g., IRQF_TRIGGER_LOW).
|
|
* @check_on_resume: Boolean describing whether to check the state of gpio
|
|
* while resuming from sleep.
|
|
*/
|
|
struct gpio_extcon_data {
|
|
struct extcon_dev *edev;
|
|
int irq;
|
|
struct delayed_work work;
|
|
unsigned long debounce_jiffies;
|
|
struct gpio_desc *gpiod;
|
|
unsigned int extcon_id;
|
|
unsigned long debounce;
|
|
unsigned long irq_flags;
|
|
bool check_on_resume;
|
|
};
|
|
|
|
static void gpio_extcon_work(struct work_struct *work)
|
|
{
|
|
int state;
|
|
struct gpio_extcon_data *data =
|
|
container_of(to_delayed_work(work), struct gpio_extcon_data,
|
|
work);
|
|
|
|
state = gpiod_get_value_cansleep(data->gpiod);
|
|
extcon_set_state_sync(data->edev, data->extcon_id, state);
|
|
}
|
|
|
|
static irqreturn_t gpio_irq_handler(int irq, void *dev_id)
|
|
{
|
|
struct gpio_extcon_data *data = dev_id;
|
|
|
|
queue_delayed_work(system_power_efficient_wq, &data->work,
|
|
data->debounce_jiffies);
|
|
return IRQ_HANDLED;
|
|
}
|
|
|
|
static int gpio_extcon_probe(struct platform_device *pdev)
|
|
{
|
|
struct gpio_extcon_data *data;
|
|
struct device *dev = &pdev->dev;
|
|
int ret;
|
|
|
|
data = devm_kzalloc(dev, sizeof(struct gpio_extcon_data), GFP_KERNEL);
|
|
if (!data)
|
|
return -ENOMEM;
|
|
|
|
/*
|
|
* FIXME: extcon_id represents the unique identifier of external
|
|
* connectors such as EXTCON_USB, EXTCON_DISP_HDMI and so on. extcon_id
|
|
* is necessary to register the extcon device. But, it's not yet
|
|
* developed to get the extcon id from device-tree or others.
|
|
* On later, it have to be solved.
|
|
*/
|
|
if (!data->irq_flags || data->extcon_id > EXTCON_NONE)
|
|
return -EINVAL;
|
|
|
|
data->gpiod = devm_gpiod_get(dev, "extcon", GPIOD_IN);
|
|
if (IS_ERR(data->gpiod))
|
|
return PTR_ERR(data->gpiod);
|
|
data->irq = gpiod_to_irq(data->gpiod);
|
|
if (data->irq <= 0)
|
|
return data->irq;
|
|
|
|
/* Allocate the memory of extcon devie and register extcon device */
|
|
data->edev = devm_extcon_dev_allocate(dev, &data->extcon_id);
|
|
if (IS_ERR(data->edev)) {
|
|
dev_err(dev, "failed to allocate extcon device\n");
|
|
return -ENOMEM;
|
|
}
|
|
|
|
ret = devm_extcon_dev_register(dev, data->edev);
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
INIT_DELAYED_WORK(&data->work, gpio_extcon_work);
|
|
|
|
/*
|
|
* Request the interrupt of gpio to detect whether external connector
|
|
* is attached or detached.
|
|
*/
|
|
ret = devm_request_any_context_irq(dev, data->irq,
|
|
gpio_irq_handler, data->irq_flags,
|
|
pdev->name, data);
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
platform_set_drvdata(pdev, data);
|
|
/* Perform initial detection */
|
|
gpio_extcon_work(&data->work.work);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int gpio_extcon_remove(struct platform_device *pdev)
|
|
{
|
|
struct gpio_extcon_data *data = platform_get_drvdata(pdev);
|
|
|
|
cancel_delayed_work_sync(&data->work);
|
|
|
|
return 0;
|
|
}
|
|
|
|
#ifdef CONFIG_PM_SLEEP
|
|
static int gpio_extcon_resume(struct device *dev)
|
|
{
|
|
struct gpio_extcon_data *data;
|
|
|
|
data = dev_get_drvdata(dev);
|
|
if (data->check_on_resume)
|
|
queue_delayed_work(system_power_efficient_wq,
|
|
&data->work, data->debounce_jiffies);
|
|
|
|
return 0;
|
|
}
|
|
#endif
|
|
|
|
static SIMPLE_DEV_PM_OPS(gpio_extcon_pm_ops, NULL, gpio_extcon_resume);
|
|
|
|
static struct platform_driver gpio_extcon_driver = {
|
|
.probe = gpio_extcon_probe,
|
|
.remove = gpio_extcon_remove,
|
|
.driver = {
|
|
.name = "extcon-gpio",
|
|
.pm = &gpio_extcon_pm_ops,
|
|
},
|
|
};
|
|
|
|
module_platform_driver(gpio_extcon_driver);
|
|
|
|
MODULE_AUTHOR("Mike Lockwood <lockwood@android.com>");
|
|
MODULE_DESCRIPTION("GPIO extcon driver");
|
|
MODULE_LICENSE("GPL");
|