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

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



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

--
To unsubscribe from this list: send the line "unsubscribe platform-driver-x86" in
the body of a message to majordomo@xxxxxxxxxxxxxxx
More majordomo info at  http://vger.kernel.org/majordomo-info.html



[Index of Archives]     [Linux Kernel Development]     [Linux USB Devel]     [Video for Linux]     [Linux Audio Users]     [Yosemite News]     [Linux Kernel]     [Linux SCSI]

  Powered by Linux