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

From: Dan O'Donovan
Date: Mon Jul 04 2016 - 12:05:31 EST


This pinctrl driver manages the Pin Control functions provided by
the I/O CPLD integrated on the UP Board. This includes dynamic pin
direction and pin mux configuration.

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

diff --git a/drivers/platform/x86/up_board_pinctrl.c b/drivers/platform/x86/up_board_pinctrl.c
new file mode 100644
index 0000000..be95837
--- /dev/null
+++ b/drivers/platform/x86/up_board_pinctrl.c
@@ -0,0 +1,285 @@
+/*
+ * UP Board I/O Header CPLD Pin Control 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/pinctrl/pinctrl.h>
+#include <linux/pinctrl/pinmux.h>
+#include <linux/pinctrl/pinconf.h>
+#include <linux/pinctrl/pinconf-generic.h>
+#include <linux/platform_device.h>
+
+#include "up_board_pinctrl.h"
+
+/* Internal context information for this driver */
+struct up_board_pinctrl {
+ struct up_board_pinctrl_pdata *pdata;
+ struct pinctrl_desc pctldesc;
+ struct pinctrl_dev *pctldev;
+};
+
+static int up_get_groups_count(struct pinctrl_dev *pctldev)
+{
+ struct up_board_pinctrl *up_pinctrl = pinctrl_dev_get_drvdata(pctldev);
+
+ return up_pinctrl->pdata->ngroup;
+}
+
+static const char *up_get_group_name(struct pinctrl_dev *pctldev,
+ unsigned int group)
+{
+ struct up_board_pinctrl *up_pinctrl = pinctrl_dev_get_drvdata(pctldev);
+
+ return up_pinctrl->pdata->groups[group].name;
+}
+
+static int up_get_group_pins(struct pinctrl_dev *pctldev, unsigned int group,
+ const unsigned int **pins, unsigned int *npins)
+{
+ struct up_board_pinctrl *up_pinctrl = pinctrl_dev_get_drvdata(pctldev);
+
+ *pins = up_pinctrl->pdata->groups[group].pins;
+ *npins = up_pinctrl->pdata->groups[group].npin;
+
+ return 0;
+}
+
+static const struct pinctrl_ops up_pinctrl_ops = {
+ .get_groups_count = up_get_groups_count,
+ .get_group_name = up_get_group_name,
+ .get_group_pins = up_get_group_pins,
+};
+
+static int up_get_functions_count(struct pinctrl_dev *pctldev)
+{
+ struct up_board_pinctrl *up_pinctrl = pinctrl_dev_get_drvdata(pctldev);
+
+ return up_pinctrl->pdata->nfunction;
+}
+
+static const char *up_get_function_name(struct pinctrl_dev *pctldev,
+ unsigned int function)
+{
+ struct up_board_pinctrl *up_pinctrl = pinctrl_dev_get_drvdata(pctldev);
+
+ return up_pinctrl->pdata->functions[function].name;
+}
+
+static int up_get_function_groups(struct pinctrl_dev *pctldev,
+ unsigned int function,
+ const char * const **groups,
+ unsigned int * const ngroups)
+{
+ struct up_board_pinctrl *up_pinctrl = pinctrl_dev_get_drvdata(pctldev);
+
+ *groups = up_pinctrl->pdata->functions[function].groups;
+ *ngroups = up_pinctrl->pdata->functions[function].ngroup;
+
+ return 0;
+}
+
+static int up_pinmux_set_mux(struct pinctrl_dev *pctldev, unsigned int function,
+ unsigned int group)
+{
+ struct up_board_pinctrl *up_pinctrl = pinctrl_dev_get_drvdata(pctldev);
+ struct up_board_pinctrl_pdata *pdata = up_pinctrl->pdata;
+ struct up_board_cpld_info *cpld_info = &up_pinctrl->pdata->cpld_info;
+ const struct up_board_pinctrl_group *grp = &pdata->groups[group];
+ int i, ret;
+
+ for (i = 0; i < grp->npin; i++) {
+ int offset = grp->pins[i];
+ struct up_board_pin_info *pin = &pdata->pins[offset];
+
+ if (pin->func_dir != UP_BOARD_PDIR_NONE) {
+ ret = cpld_info->reg_set_bit(cpld_info->cpld,
+ pin->dir_ctrl_offset,
+ pin->func_dir);
+ if (ret)
+ return ret;
+ }
+ if (pin->mux_ctrl_offset != UP_BOARD_UNASSIGNED) {
+ ret = cpld_info->reg_set_bit(cpld_info->cpld,
+ pin->mux_ctrl_offset,
+ UP_BOARD_PMUX_FUNC);
+ if (ret)
+ return ret;
+ }
+ pin->func_enabled = true;
+ }
+
+ return 0;
+}
+
+static int up_gpio_set_direction(struct pinctrl_dev *pctldev,
+ struct pinctrl_gpio_range *range,
+ unsigned int offset, bool input)
+{
+ struct up_board_pinctrl *up_pinctrl = pinctrl_dev_get_drvdata(pctldev);
+ struct up_board_pinctrl_pdata *pdata = up_pinctrl->pdata;
+ struct up_board_cpld_info *cpld_info = &up_pinctrl->pdata->cpld_info;
+ struct up_board_pin_info *pin = &pdata->pins[offset];
+ int dir = input ? UP_BOARD_PDIR_IN : UP_BOARD_PDIR_OUT;
+
+ return cpld_info->reg_set_bit(cpld_info->cpld,
+ pin->dir_ctrl_offset,
+ dir);
+}
+
+static int up_gpio_request_enable(struct pinctrl_dev *pctldev,
+ struct pinctrl_gpio_range *range,
+ unsigned int offset)
+{
+ struct up_board_pinctrl *up_pinctrl = pinctrl_dev_get_drvdata(pctldev);
+ struct up_board_pinctrl_pdata *pdata = up_pinctrl->pdata;
+ struct up_board_cpld_info *cpld_info = &up_pinctrl->pdata->cpld_info;
+ struct up_board_pin_info *pin = &pdata->pins[offset];
+ int ret;
+
+ if (pin->mux_ctrl_offset != UP_BOARD_UNASSIGNED) {
+ ret = cpld_info->reg_set_bit(cpld_info->cpld,
+ pin->mux_ctrl_offset,
+ UP_BOARD_PMUX_GPIO);
+ if (ret)
+ return ret;
+ }
+
+ return 0;
+}
+
+static void up_gpio_disable_free(struct pinctrl_dev *pctldev,
+ struct pinctrl_gpio_range *range,
+ unsigned int offset)
+{
+ struct up_board_pinctrl *up_pinctrl = pinctrl_dev_get_drvdata(pctldev);
+ struct up_board_pinctrl_pdata *pdata = up_pinctrl->pdata;
+ struct up_board_cpld_info *cpld_info = &up_pinctrl->pdata->cpld_info;
+ struct up_board_pin_info *pin = &pdata->pins[offset];
+
+ if (pin->func_enabled) {
+ if (pin->func_dir != UP_BOARD_PDIR_NONE) {
+ cpld_info->reg_set_bit(cpld_info->cpld,
+ pin->dir_ctrl_offset,
+ pin->func_dir);
+ }
+ if (pin->mux_ctrl_offset != UP_BOARD_UNASSIGNED) {
+ cpld_info->reg_set_bit(cpld_info->cpld,
+ pin->mux_ctrl_offset,
+ UP_BOARD_PMUX_FUNC);
+ }
+ }
+}
+
+static const struct pinmux_ops up_pinmux_ops = {
+ .get_functions_count = up_get_functions_count,
+ .get_function_name = up_get_function_name,
+ .get_function_groups = up_get_function_groups,
+ .set_mux = up_pinmux_set_mux,
+ .gpio_request_enable = up_gpio_request_enable,
+ .gpio_disable_free = up_gpio_disable_free,
+ .gpio_set_direction = up_gpio_set_direction,
+};
+
+static int up_config_get(struct pinctrl_dev *pctldev, unsigned int pin,
+ unsigned long *config)
+{
+ return -ENOTSUPP;
+}
+
+static int up_config_set(struct pinctrl_dev *pctldev, unsigned int pin,
+ unsigned long *configs, unsigned int nconfigs)
+{
+ return 0;
+}
+
+static const struct pinconf_ops up_pinconf_ops = {
+ .is_generic = true,
+ .pin_config_set = up_config_set,
+ .pin_config_get = up_config_get,
+};
+
+static struct pinctrl_desc up_pinctrl_desc = {
+ .owner = THIS_MODULE,
+ .pctlops = &up_pinctrl_ops,
+ .pmxops = &up_pinmux_ops,
+ .confops = &up_pinconf_ops,
+};
+
+static int up_board_pinctrl_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct up_board_pinctrl_pdata *pdata = dev_get_platdata(dev);
+ struct up_board_pinctrl *up_pinctrl;
+
+ if (!pdata)
+ return -EINVAL;
+
+ up_pinctrl = devm_kzalloc(dev, sizeof(*up_pinctrl), GFP_KERNEL);
+ if (!up_pinctrl)
+ return -ENOMEM;
+
+ platform_set_drvdata(pdev, up_pinctrl);
+
+ up_pinctrl->pdata = pdata;
+ up_pinctrl->pctldesc = up_pinctrl_desc;
+ up_pinctrl->pctldesc.pins = pdata->descs;
+ up_pinctrl->pctldesc.npins = pdata->ndesc;
+ up_pinctrl->pctldesc.name = dev_name(dev);
+ up_pinctrl->pctldev = pinctrl_register(&up_pinctrl->pctldesc,
+ dev, up_pinctrl);
+ if (IS_ERR(up_pinctrl->pctldev)) {
+ dev_err(dev, "failed to register pinctrl driver\n");
+ return PTR_ERR(up_pinctrl->pctldev);
+ }
+
+ return 0;
+}
+
+static int up_board_pinctrl_remove(struct platform_device *pdev)
+{
+ struct up_board_pinctrl *up_pinctrl = platform_get_drvdata(pdev);
+
+ pinctrl_unregister(up_pinctrl->pctldev);
+
+ return 0;
+}
+
+static struct platform_driver up_board_pinctrl_driver = {
+ .driver.name = "up-board-pinctrl",
+ .driver.owner = THIS_MODULE,
+ .probe = up_board_pinctrl_probe,
+ .remove = up_board_pinctrl_remove,
+};
+
+static int __init up_board_pinctrl_init(void)
+{
+ return platform_driver_register(&up_board_pinctrl_driver);
+}
+subsys_initcall(up_board_pinctrl_init);
+
+static void __exit up_board_pinctrl_exit(void)
+{
+ platform_driver_unregister(&up_board_pinctrl_driver);
+}
+module_exit(up_board_pinctrl_exit);
+
+MODULE_AUTHOR("Dan O'Donovan <dan@xxxxxxxxxx>");
+MODULE_DESCRIPTION("UP Board I/O Header CPLD Pin Control driver");
+MODULE_LICENSE("GPL v2");
+MODULE_ALIAS("platform:up-board-pinctrl");
diff --git a/drivers/platform/x86/up_board_pinctrl.h b/drivers/platform/x86/up_board_pinctrl.h
new file mode 100644
index 0000000..ce46886
--- /dev/null
+++ b/drivers/platform/x86/up_board_pinctrl.h
@@ -0,0 +1,102 @@
+/*
+ * 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_PINCTRL_H_
+#define _UP_BOARD_PINCTRL_H_
+
+#include <linux/pinctrl/pinctrl.h>
+
+#include "up_board_cpld.h"
+
+#define UP_BOARD_PDIR_NONE -1
+#define UP_BOARD_PDIR_OUT 0
+#define UP_BOARD_PDIR_IN 1
+
+#define UP_BOARD_PMUX_GPIO 0
+#define UP_BOARD_PMUX_FUNC 1
+
+#define UP_BOARD_UNASSIGNED -1
+
+/**
+ * struct up_board_pinctrl_group - information for a single pinctrl group
+ * @name: group name
+ * @pins: array of pins associated with this group
+ * @npin: size of pins array
+ */
+struct up_board_pinctrl_group {
+ const char *name;
+ const unsigned int *pins;
+ size_t npin;
+};
+
+/**
+ * struct up_board_pinctrl_function - information for a single pinctrl function
+ * @name: function name
+ * @groups: array of groups associated with this function
+ * @ngroup: size of groups array
+ */
+struct up_board_pinctrl_function {
+ const char *name;
+ const char * const *groups;
+ size_t ngroup;
+};
+
+/**
+ * struct up_board_pin_info - information for each UP Board GPIO pin
+ * @dir_ctrl_offset: CPLD register bit offset for pin direction control
+ * @mux_ctrl_offset: CPLD register bit offset for pin mux control
+ * @func_dir: Pin dir to set when alternate pin function is selected
+ * @func_enabled: Flag to indicate if alternate pin function is enabled
+ *
+ * Information for a single GPIO pin on the UP Board I/O header, including
+ * details of CPLD parameters for managing pin direction and function selection.
+ */
+struct up_board_pin_info {
+ int dir_ctrl_offset;
+ int mux_ctrl_offset;
+ int func_dir;
+ bool func_enabled;
+};
+
+/**
+ * struct up_board_pinctrl_pdata - platform driver data
+ * @cpld_info: CPLD configuration interface information
+ * @pins: Array of pin information structures
+ * @npin: Number of entries in pins array
+ * @descs: Array of pinctrl pin descriptors
+ * @ndesc: Number of entries in pin_descs array
+ * @groups: Array of pin groups
+ * @ngroup: Number of entries in groups array
+ * @functions: Array of pin functions
+ * @nfunction: Number of entries in functions array
+ *
+ * Platform data provided to UP Board CPLD pinctrl platform device driver.
+ * Provides information for each GPIO pin on the UP Board I/O header.
+ */
+struct up_board_pinctrl_pdata {
+ struct up_board_cpld_info cpld_info;
+ struct up_board_pin_info *pins;
+ size_t npin;
+ const struct pinctrl_pin_desc *descs;
+ size_t ndesc;
+ const struct up_board_pinctrl_group *groups;
+ size_t ngroup;
+ const struct up_board_pinctrl_function *functions;
+ size_t nfunction;
+};
+
+#endif /* _UP_BOARD_PINCTRL_H_ */
--
2.1.4