72daceb9a1
The driver uses devm_gpiod_get_index(..., index) so that the index refers directly to the GpioIo resource under the ACPI device. The problem with this is that if the ordering changes we get wrong GPIOs. With ACPI 5.1 _DSD we can now use names instead to reference GPIOs analogous to Device Tree. However, we still have systems out there that do not provide _DSD at all. These systems must be supported as well. Luckily we now have acpi_dev_add_driver_gpios() that can be used to provide mappings for systems where _DSD is not provided and still take advantage of _DSD if it exists. This patch changes the driver to create default GPIO mappings if we are running on ACPI system. While there we can drop the indices completely and use devm_gpiod_get() with name instead. Signed-off-by: Mika Westerberg <mika.westerberg@linux.intel.com> Reviewed-by: Johannes Berg <johannes@sipsolutions.net> Acked-by: John W. Linville <linville@tuxdriver.com> Acked-by: Linus Walleij <linus.walleij@linaro.org> Signed-off-by: Rafael J. Wysocki <rafael.j.wysocki@intel.com>
196 lines
4.9 KiB
C
196 lines
4.9 KiB
C
/*
|
|
* Copyright (c) 2011, NVIDIA Corporation.
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation; either version 2 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* 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.
|
|
*
|
|
* You should have received a copy of the GNU General Public License along
|
|
* with this program; if not, write to the Free Software Foundation, Inc.,
|
|
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
|
*/
|
|
|
|
#include <linux/gpio.h>
|
|
#include <linux/init.h>
|
|
#include <linux/kernel.h>
|
|
#include <linux/module.h>
|
|
#include <linux/rfkill.h>
|
|
#include <linux/platform_device.h>
|
|
#include <linux/clk.h>
|
|
#include <linux/slab.h>
|
|
#include <linux/acpi.h>
|
|
#include <linux/gpio/consumer.h>
|
|
|
|
#include <linux/rfkill-gpio.h>
|
|
|
|
struct rfkill_gpio_data {
|
|
const char *name;
|
|
enum rfkill_type type;
|
|
struct gpio_desc *reset_gpio;
|
|
struct gpio_desc *shutdown_gpio;
|
|
|
|
struct rfkill *rfkill_dev;
|
|
struct clk *clk;
|
|
|
|
bool clk_enabled;
|
|
};
|
|
|
|
static int rfkill_gpio_set_power(void *data, bool blocked)
|
|
{
|
|
struct rfkill_gpio_data *rfkill = data;
|
|
|
|
if (!blocked && !IS_ERR(rfkill->clk) && !rfkill->clk_enabled)
|
|
clk_enable(rfkill->clk);
|
|
|
|
gpiod_set_value_cansleep(rfkill->shutdown_gpio, !blocked);
|
|
gpiod_set_value_cansleep(rfkill->reset_gpio, !blocked);
|
|
|
|
if (blocked && !IS_ERR(rfkill->clk) && rfkill->clk_enabled)
|
|
clk_disable(rfkill->clk);
|
|
|
|
rfkill->clk_enabled = !blocked;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static const struct rfkill_ops rfkill_gpio_ops = {
|
|
.set_block = rfkill_gpio_set_power,
|
|
};
|
|
|
|
static const struct acpi_gpio_params reset_gpios = { 0, 0, false };
|
|
static const struct acpi_gpio_params shutdown_gpios = { 1, 0, false };
|
|
|
|
static const struct acpi_gpio_mapping acpi_rfkill_default_gpios[] = {
|
|
{ "reset-gpios", &reset_gpios, 1 },
|
|
{ "shutdown-gpios", &shutdown_gpios, 1 },
|
|
{ },
|
|
};
|
|
|
|
static int rfkill_gpio_acpi_probe(struct device *dev,
|
|
struct rfkill_gpio_data *rfkill)
|
|
{
|
|
const struct acpi_device_id *id;
|
|
|
|
id = acpi_match_device(dev->driver->acpi_match_table, dev);
|
|
if (!id)
|
|
return -ENODEV;
|
|
|
|
rfkill->name = dev_name(dev);
|
|
rfkill->type = (unsigned)id->driver_data;
|
|
|
|
return acpi_dev_add_driver_gpios(ACPI_COMPANION(dev),
|
|
acpi_rfkill_default_gpios);
|
|
}
|
|
|
|
static int rfkill_gpio_probe(struct platform_device *pdev)
|
|
{
|
|
struct rfkill_gpio_platform_data *pdata = pdev->dev.platform_data;
|
|
struct rfkill_gpio_data *rfkill;
|
|
struct gpio_desc *gpio;
|
|
int ret;
|
|
|
|
rfkill = devm_kzalloc(&pdev->dev, sizeof(*rfkill), GFP_KERNEL);
|
|
if (!rfkill)
|
|
return -ENOMEM;
|
|
|
|
if (ACPI_HANDLE(&pdev->dev)) {
|
|
ret = rfkill_gpio_acpi_probe(&pdev->dev, rfkill);
|
|
if (ret)
|
|
return ret;
|
|
} else if (pdata) {
|
|
rfkill->name = pdata->name;
|
|
rfkill->type = pdata->type;
|
|
} else {
|
|
return -ENODEV;
|
|
}
|
|
|
|
rfkill->clk = devm_clk_get(&pdev->dev, NULL);
|
|
|
|
gpio = devm_gpiod_get(&pdev->dev, "reset");
|
|
if (!IS_ERR(gpio)) {
|
|
ret = gpiod_direction_output(gpio, 0);
|
|
if (ret)
|
|
return ret;
|
|
rfkill->reset_gpio = gpio;
|
|
}
|
|
|
|
gpio = devm_gpiod_get(&pdev->dev, "shutdown");
|
|
if (!IS_ERR(gpio)) {
|
|
ret = gpiod_direction_output(gpio, 0);
|
|
if (ret)
|
|
return ret;
|
|
rfkill->shutdown_gpio = gpio;
|
|
}
|
|
|
|
/* Make sure at-least one of the GPIO is defined and that
|
|
* a name is specified for this instance
|
|
*/
|
|
if ((!rfkill->reset_gpio && !rfkill->shutdown_gpio) || !rfkill->name) {
|
|
dev_err(&pdev->dev, "invalid platform data\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
rfkill->rfkill_dev = rfkill_alloc(rfkill->name, &pdev->dev,
|
|
rfkill->type, &rfkill_gpio_ops,
|
|
rfkill);
|
|
if (!rfkill->rfkill_dev)
|
|
return -ENOMEM;
|
|
|
|
ret = rfkill_register(rfkill->rfkill_dev);
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
platform_set_drvdata(pdev, rfkill);
|
|
|
|
dev_info(&pdev->dev, "%s device registered.\n", rfkill->name);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int rfkill_gpio_remove(struct platform_device *pdev)
|
|
{
|
|
struct rfkill_gpio_data *rfkill = platform_get_drvdata(pdev);
|
|
|
|
rfkill_unregister(rfkill->rfkill_dev);
|
|
rfkill_destroy(rfkill->rfkill_dev);
|
|
|
|
acpi_dev_remove_driver_gpios(ACPI_COMPANION(&pdev->dev));
|
|
|
|
return 0;
|
|
}
|
|
|
|
#ifdef CONFIG_ACPI
|
|
static const struct acpi_device_id rfkill_acpi_match[] = {
|
|
{ "BCM2E1A", RFKILL_TYPE_BLUETOOTH },
|
|
{ "BCM2E39", RFKILL_TYPE_BLUETOOTH },
|
|
{ "BCM2E3D", RFKILL_TYPE_BLUETOOTH },
|
|
{ "BCM2E64", RFKILL_TYPE_BLUETOOTH },
|
|
{ "BCM4752", RFKILL_TYPE_GPS },
|
|
{ "LNV4752", RFKILL_TYPE_GPS },
|
|
{ },
|
|
};
|
|
MODULE_DEVICE_TABLE(acpi, rfkill_acpi_match);
|
|
#endif
|
|
|
|
static struct platform_driver rfkill_gpio_driver = {
|
|
.probe = rfkill_gpio_probe,
|
|
.remove = rfkill_gpio_remove,
|
|
.driver = {
|
|
.name = "rfkill_gpio",
|
|
.owner = THIS_MODULE,
|
|
.acpi_match_table = ACPI_PTR(rfkill_acpi_match),
|
|
},
|
|
};
|
|
|
|
module_platform_driver(rfkill_gpio_driver);
|
|
|
|
MODULE_DESCRIPTION("gpio rfkill");
|
|
MODULE_AUTHOR("NVIDIA");
|
|
MODULE_LICENSE("GPL");
|