blob: 4e4ee58230aeae0a44326cbde220053f9fa96108 [file] [log] [blame]
/*
* Copyright (C) 2014 Nest Labs
*
* 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 version 2.
*
* This program is distributed "as is" WITHOUT ANY WARRANTY of any
* kind, whether express or implied; without even the implied warranty
* of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*/
#include <linux/device.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/sched.h>
#include <linux/interrupt.h>
#include <linux/platform_device.h>
#include <linux/io.h>
#include <linux/slab.h>
#include <linux/of.h>
#include <linux/of_gpio.h>
struct nl_gpio_exporter_dev {
struct device *device;
};
/* Helper functions to leverage devres infrastructure */
static void devm_gpio_export_release(struct device *dev, void *res)
{
unsigned *gpio = res;
gpio_unexport(*gpio);
}
static int devm_gpio_export(struct device *dev, unsigned gpio, bool direction_may_change)
{
unsigned *dr;
int rc;
dr = devres_alloc(devm_gpio_export_release, sizeof(unsigned), GFP_KERNEL);
if (!dr)
return -ENOMEM;
rc = gpio_export(gpio, direction_may_change);
if (rc) {
devres_free(dr);
return rc;
}
*dr = gpio;
devres_add(dev, dr);
return 0;
}
/* Scan through the DT and export GPIOs found using the provided flags */
static int nl_gpio_exporter_parse_dt(struct nl_gpio_exporter_dev *dev,
const char *names_string,
const char *gpios_string,
int flags)
{
struct property *prop;
const char *string;
int gpio, index = 0;
enum of_gpio_flags of_flags;
of_property_for_each_string(dev->device->of_node, names_string, prop, string) {
gpio = of_get_named_gpio_flags(dev->device->of_node,
gpios_string, index, &of_flags);
if (!gpio_is_valid(gpio)) {
dev_err(dev->device, "Couldn't get GPIO (%s)\n", string);
return -ENODEV;
}
if (of_flags & OF_GPIO_ACTIVE_LOW)
flags |= (GPIOF_ACTIVE_LOW | GPIOF_INIT_HIGH);
else
flags &= ~(GPIOF_ACTIVE_LOW | GPIOF_INIT_HIGH);
if (devm_gpio_request_one(dev->device, gpio, flags, string)) {
dev_err(dev->device, "Couldn't request GPIO %d (%s)\n", gpio, string);
return -ENODEV;
}
if (devm_gpio_export(dev->device, gpio, 1)) {
dev_err(dev->device, "Couldn't export GPIO %d (%s)\n", gpio, string);
return -ENODEV;
}
if (gpio_export_link(dev->device, string, gpio)) {
dev_err(dev->device, "Couldn't create link for GPIO %d (%s)\n", gpio, string);
return -ENODEV;
}
dev_info(dev->device, "successfully exported %s as GPIO %d\n", string, gpio);
index++;
}
return 0;
}
static int nl_gpio_exporter_probe(struct platform_device *pdev)
{
struct nl_gpio_exporter_dev *dev;
int ret;
/* Allocate memory for private data structure */
dev = devm_kzalloc(&pdev->dev, sizeof(*dev), GFP_KERNEL);
if (!dev)
return -ENOMEM;
platform_set_drvdata(pdev, dev);
dev->device = &pdev->dev;
/* Make sure there is a DT node */
if (!dev->device->of_node)
return -ENODEV;
/* Read Device Tree and export intput GPIOs */
ret = nl_gpio_exporter_parse_dt(dev, "input-names", "input-gpios", GPIOF_DIR_IN);
if (ret)
return ret;
/* Read Device Tree and export output GPIOs */
return nl_gpio_exporter_parse_dt(dev, "output-names", "output-gpios", GPIOF_DIR_OUT);
}
static int nl_gpio_exporter_remove(struct platform_device *pdev)
{
return 0;
}
static const struct of_device_id nl_gpio_exporter_of_match[] = {
{.compatible = "nestlabs,gpio-exporter",},
{},
};
MODULE_DEVICE_TABLE(of, nl_gpio_exporter_of_match);
static struct platform_driver nl_gpio_exporter_driver = {
.driver = {
.name = "nl-gpio-exporter",
.owner = THIS_MODULE,
.of_match_table = nl_gpio_exporter_of_match,
},
.probe = nl_gpio_exporter_probe,
.remove = nl_gpio_exporter_remove,
};
module_platform_driver(nl_gpio_exporter_driver);
MODULE_DESCRIPTION("Nestlabs GPIO Exporter");
MODULE_LICENSE("GPL v2");