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 -- 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