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 <d...@emutex.com> --- 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 <d...@emutex.com> + * + * 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 <d...@emutex.com>"); +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 <d...@emutex.com> + * + * 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