[RESEND RFC PATCH 3/5] platform: x86: add UP Board I/O gpio driver

From: Dan O'Donovan
Date: Mon Jul 04 2016 - 12:06:20 EST


This gpio driver encapsulates the GPIO pin mapping function provided
by the I/O CPLD integrated on the UP Board. This makes possible the
following features:
- integration of UP Board pin control functions with GPIO run-time
configuration hooks, to allow run-time pin direction and pin mux
selection
- logical numbering scheme for the external GPIO pins which is
independent from the internal mapping to SoC GPIO pins. This
allows for variations in CPLD pin mapping configurations.
- numbering scheme which mimicks the Raspberry Pi GPIO numbering to
facilitate application portability, reflecting the hardware design
goal for the UP Board I/O pin header.

In essence, this driver is implemented as a thin layer on top of the
underlying Cherry Trail SoC GPIO driver, and instantiates a logical
gpiochip device. The intention is that the user at application level
would utilise this logical GPIO chip to access the GPIO functions on
the UP Board I/O pins.

Signed-off-by: Dan O'Donovan <dan@xxxxxxxxxx>
---
drivers/platform/x86/up_board_gpio.c | 254 +++++++++++++++++++++++++++++++++++
drivers/platform/x86/up_board_gpio.h | 59 ++++++++
2 files changed, 313 insertions(+)
create mode 100644 drivers/platform/x86/up_board_gpio.c
create mode 100644 drivers/platform/x86/up_board_gpio.h

diff --git a/drivers/platform/x86/up_board_gpio.c b/drivers/platform/x86/up_board_gpio.c
new file mode 100644
index 0000000..c30c64b
--- /dev/null
+++ b/drivers/platform/x86/up_board_gpio.c
@@ -0,0 +1,254 @@
+/*
+ * UP Board I/O Header CPLD GPIO driver.
+ *
+ * Copyright (c) 2016, Emutex Ltd. All rights reserved.
+ *
+ * Author: Dan O'Donovan <dan@xxxxxxxxxx>
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU General Public License,
+ * version 2, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope 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, see <http://www.gnu.org/licenses/>.
+ */
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/gpio.h>
+#include <linux/pinctrl/pinctrl.h>
+#include <linux/platform_device.h>
+#include <linux/interrupt.h>
+
+#include "up_board_gpio.h"
+
+/* Internal context information for this driver */
+struct up_board_gpio {
+ struct up_board_gpio_pdata *pdata;
+ struct gpio_chip chip;
+};
+
+static irqreturn_t up_gpio_irq_handler(int irq, void *data)
+{
+ struct up_board_gpio_info *gpio = data;
+
+ generic_handle_irq(gpio->irq);
+ return IRQ_HANDLED;
+}
+
+static unsigned int up_gpio_irq_startup(struct irq_data *data)
+{
+ struct gpio_chip *gc = irq_data_get_irq_chip_data(data);
+ struct up_board_gpio *up_gpio = gpiochip_get_data(gc);
+ unsigned int offset = irqd_to_hwirq(data);
+ struct up_board_gpio_info *gpio = &up_gpio->pdata->gpios[offset];
+
+ return request_irq(gpio->soc_gpio_irq, up_gpio_irq_handler,
+ IRQF_ONESHOT, gc->label, gpio);
+}
+
+static void up_gpio_irq_shutdown(struct irq_data *data)
+{
+ struct gpio_chip *gc = irq_data_get_irq_chip_data(data);
+ struct up_board_gpio *up_gpio = gpiochip_get_data(gc);
+ unsigned int offset = irqd_to_hwirq(data);
+ struct up_board_gpio_info *gpio = &up_gpio->pdata->gpios[offset];
+
+ free_irq(gpio->soc_gpio_irq, gpio);
+}
+
+static struct irq_chip up_gpio_irqchip = {
+ .irq_startup = up_gpio_irq_startup,
+ .irq_shutdown = up_gpio_irq_shutdown,
+ .irq_enable = irq_chip_enable_parent,
+ .irq_disable = irq_chip_disable_parent,
+ .irq_mask = irq_chip_mask_parent,
+ .irq_unmask = irq_chip_unmask_parent,
+ .irq_ack = irq_chip_ack_parent,
+ .irq_set_type = irq_chip_set_type_parent,
+};
+
+static int up_gpio_dir_in(struct gpio_chip *gc, unsigned int offset)
+{
+ struct up_board_gpio *up_gpio = gpiochip_get_data(gc);
+ struct up_board_gpio_info *gpio = &up_gpio->pdata->gpios[offset];
+ int ret;
+
+ ret = gpiod_direction_input(gpio->soc_gpiod);
+ if (ret)
+ return ret;
+
+ return pinctrl_gpio_direction_input(gc->base + offset);
+}
+
+static int up_gpio_dir_out(struct gpio_chip *gc, unsigned int offset, int value)
+{
+ struct up_board_gpio *up_gpio = gpiochip_get_data(gc);
+ struct up_board_gpio_info *gpio = &up_gpio->pdata->gpios[offset];
+ int ret;
+
+ ret = pinctrl_gpio_direction_output(gc->base + offset);
+ if (ret)
+ return ret;
+
+ return gpiod_direction_output(gpio->soc_gpiod, value);
+}
+
+static int up_gpio_get_dir(struct gpio_chip *gc, unsigned int offset)
+{
+ struct up_board_gpio *up_gpio = gpiochip_get_data(gc);
+ struct up_board_gpio_info *gpio = &up_gpio->pdata->gpios[offset];
+
+ return gpiod_get_direction(gpio->soc_gpiod);
+}
+
+static int up_gpio_request(struct gpio_chip *gc, unsigned int offset)
+{
+ struct up_board_gpio *up_gpio = gpiochip_get_data(gc);
+ struct up_board_gpio_info *gpio = &up_gpio->pdata->gpios[offset];
+ int ret;
+
+ ret = pinctrl_request_gpio(gc->base + offset);
+ if (ret)
+ return ret;
+
+ if (gpiod_get_direction(gpio->soc_gpiod))
+ ret = pinctrl_gpio_direction_input(gc->base + offset);
+ else
+ ret = pinctrl_gpio_direction_output(gc->base + offset);
+ if (ret)
+ return ret;
+
+ return gpio_request(gpio->soc_gpio, gc->label);
+}
+
+static void up_gpio_free(struct gpio_chip *gc, unsigned int offset)
+{
+ struct up_board_gpio *up_gpio = gpiochip_get_data(gc);
+ struct up_board_gpio_info *gpio = &up_gpio->pdata->gpios[offset];
+
+ pinctrl_free_gpio(gc->base + offset);
+ gpio_free(gpio->soc_gpio);
+}
+
+static int up_gpio_get(struct gpio_chip *gc, unsigned int offset)
+{
+ struct up_board_gpio *up_gpio = gpiochip_get_data(gc);
+ struct up_board_gpio_info *gpio = &up_gpio->pdata->gpios[offset];
+
+ return gpiod_get_value(gpio->soc_gpiod);
+}
+
+static void up_gpio_set(struct gpio_chip *gc, unsigned int offset, int value)
+{
+ struct up_board_gpio *up_gpio = gpiochip_get_data(gc);
+ struct up_board_gpio_info *gpio = &up_gpio->pdata->gpios[offset];
+
+ gpiod_set_value(gpio->soc_gpiod, value);
+}
+
+static struct gpio_chip up_gpio_chip = {
+ .owner = THIS_MODULE,
+ .request = up_gpio_request,
+ .free = up_gpio_free,
+ .get_direction = up_gpio_get_dir,
+ .direction_input = up_gpio_dir_in,
+ .direction_output = up_gpio_dir_out,
+ .get = up_gpio_get,
+ .set = up_gpio_set,
+};
+
+static int up_board_gpio_setup(struct up_board_gpio *up_gpio)
+{
+ struct up_board_gpio_pdata *pdata = up_gpio->pdata;
+ size_t i;
+
+ for (i = 0; i < pdata->ngpio; i++) {
+ struct up_board_gpio_info *gpio = &pdata->gpios[i];
+ struct irq_data *irq_data;
+
+ /*
+ * Create parent linkage with SoC GPIO IRQs to simplify
+ * IRQ handling by enabling use of irq_chip_*_parent()
+ * functions
+ */
+ gpio->soc_gpio_irq = gpiod_to_irq(gpio->soc_gpiod);
+ gpio->irq = irq_find_mapping(up_gpio->chip.irqdomain, i);
+ irq_set_parent(gpio->irq, gpio->soc_gpio_irq);
+ irq_data = irq_get_irq_data(gpio->irq);
+ irq_data->parent_data = irq_get_irq_data(gpio->soc_gpio_irq);
+ }
+
+ return 0;
+}
+
+static int up_board_gpio_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct up_board_gpio_pdata *pdata = dev_get_platdata(dev);
+ struct up_board_gpio *up_gpio;
+ int ret;
+
+ if (!pdata)
+ return -EINVAL;
+
+ up_gpio = devm_kzalloc(dev, sizeof(*up_gpio), GFP_KERNEL);
+ if (!up_gpio)
+ return -ENOMEM;
+
+ up_gpio->pdata = pdata;
+ up_gpio->chip = up_gpio_chip;
+ up_gpio->chip.parent = dev;
+ up_gpio->chip.ngpio = pdata->ngpio;
+ up_gpio->chip.label = dev_name(dev);
+
+ ret = devm_gpiochip_add_data(dev, &up_gpio->chip, up_gpio);
+ if (ret) {
+ dev_err(dev, "failed to add gpio chip: %d\n", ret);
+ return ret;
+ }
+
+ ret = gpiochip_add_pin_range(&up_gpio->chip, "up-board-pinctrl", 0, 0,
+ pdata->ngpio);
+ if (ret) {
+ dev_err(dev, "failed to add GPIO pin range\n");
+ return ret;
+ }
+
+ up_gpio_irqchip.name = up_gpio->chip.label;
+ ret = gpiochip_irqchip_add(&up_gpio->chip, &up_gpio_irqchip, 0,
+ handle_simple_irq, IRQ_TYPE_NONE);
+ if (ret) {
+ dev_err(dev, "failed to add IRQ chip\n");
+ goto fail_irqchip_add;
+ }
+
+ ret = up_board_gpio_setup(up_gpio);
+ if (ret)
+ goto fail_gpio_setup;
+
+ return 0;
+
+fail_gpio_setup:
+fail_irqchip_add:
+ gpiochip_remove_pin_ranges(&up_gpio->chip);
+
+ return ret;
+}
+
+static struct platform_driver up_board_gpio_driver = {
+ .driver.name = "up-board-gpio",
+ .driver.owner = THIS_MODULE,
+ .probe = up_board_gpio_probe,
+};
+
+module_platform_driver(up_board_gpio_driver);
+
+MODULE_AUTHOR("Dan O'Donovan <dan@xxxxxxxxxx>");
+MODULE_DESCRIPTION("UP Board I/O Header CPLD GPIO driver");
+MODULE_LICENSE("GPL v2");
+MODULE_ALIAS("platform:up-board-gpio");
diff --git a/drivers/platform/x86/up_board_gpio.h b/drivers/platform/x86/up_board_gpio.h
new file mode 100644
index 0000000..ada7d2e
--- /dev/null
+++ b/drivers/platform/x86/up_board_gpio.h
@@ -0,0 +1,59 @@
+/*
+ * Copyright (c) 2016, Emutex Ltd. All rights reserved.
+ *
+ * Author: Dan O'Donovan <dan@xxxxxxxxxx>
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU General Public License,
+ * version 2, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope 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, see <http://www.gnu.org/licenses/>.
+ */
+#ifndef _UP_BOARD_GPIO_H_
+#define _UP_BOARD_GPIO_H_
+
+/**
+ * struct up_board_gpio_info - information for an UP Board GPIO pin
+ * @soc_gc_name: Device name for corresponding SoC GPIO chip
+ * @soc_gc_offset: GPIO chip offset of corresponding SoC GPIO pin
+ * @soc_gc: SoC GPIO chip reference
+ * @soc_gpiod: SoC GPIO descriptor reference
+ * @soc_gpio: SoC GPIO assigned pin number
+ * @soc_gpio_irq: SoC GPIO assigned IRQ number
+ * @soc_gpio_flags: Optional GPIO flags to apply to SoC GPIO
+ * @irq: Assigned IRQ number for this GPIO pin
+ *
+ * Information for a single GPIO pin on the UP Board I/O header, including
+ * details of the corresponding SoC GPIO mapped to this I/O header GPIO.
+ */
+struct up_board_gpio_info {
+ char *soc_gc_name;
+ unsigned int soc_gc_offset;
+ struct gpio_chip *soc_gc;
+ struct gpio_desc *soc_gpiod;
+ int soc_gpio;
+ int soc_gpio_irq;
+ int soc_gpio_flags;
+ int irq;
+};
+
+/**
+ * struct up_board_gpio_pdata - platform driver data
+ * @gpios: Array of GPIO information structures.
+ * @ngpio: Number of entries in gpios array.
+ *
+ * Platform data provided to UP Board CPLD GPIO platform device driver.
+ * Provides information for each GPIO pin on the UP Board I/O header.
+ */
+struct up_board_gpio_pdata {
+ struct up_board_gpio_info *gpios;
+ size_t ngpio;
+};
+
+#endif /* _UP_BOARD_GPIO_H_ */
--
2.1.4