diff options
Diffstat (limited to 'drivers/pinctrl/pinctrl-mvebu.c')
-rw-r--r-- | drivers/pinctrl/pinctrl-mvebu.c | 754 |
1 files changed, 754 insertions, 0 deletions
diff --git a/drivers/pinctrl/pinctrl-mvebu.c b/drivers/pinctrl/pinctrl-mvebu.c new file mode 100644 index 000000000000..8e6266c6249a --- /dev/null +++ b/drivers/pinctrl/pinctrl-mvebu.c @@ -0,0 +1,754 @@ +/* + * Marvell MVEBU pinctrl core driver + * + * Authors: Sebastian Hesselbarth <sebastian.hesselbarth@gmail.com> + * Thomas Petazzoni <thomas.petazzoni@free-electrons.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + */ + +#include <linux/platform_device.h> +#include <linux/module.h> +#include <linux/slab.h> +#include <linux/io.h> +#include <linux/of.h> +#include <linux/of_address.h> +#include <linux/of_platform.h> +#include <linux/err.h> +#include <linux/gpio.h> +#include <linux/pinctrl/machine.h> +#include <linux/pinctrl/pinconf.h> +#include <linux/pinctrl/pinctrl.h> +#include <linux/pinctrl/pinmux.h> + +#include "core.h" +#include "pinctrl-mvebu.h" + +#define MPPS_PER_REG 8 +#define MPP_BITS 4 +#define MPP_MASK 0xf + +struct mvebu_pinctrl_function { + const char *name; + const char **groups; + unsigned num_groups; +}; + +struct mvebu_pinctrl_group { + const char *name; + struct mvebu_mpp_ctrl *ctrl; + struct mvebu_mpp_ctrl_setting *settings; + unsigned num_settings; + unsigned gid; + unsigned *pins; + unsigned npins; +}; + +struct mvebu_pinctrl { + struct device *dev; + struct pinctrl_dev *pctldev; + struct pinctrl_desc desc; + void __iomem *base; + struct mvebu_pinctrl_group *groups; + unsigned num_groups; + struct mvebu_pinctrl_function *functions; + unsigned num_functions; + u8 variant; +}; + +static struct mvebu_pinctrl_group *mvebu_pinctrl_find_group_by_pid( + struct mvebu_pinctrl *pctl, unsigned pid) +{ + unsigned n; + for (n = 0; n < pctl->num_groups; n++) { + if (pid >= pctl->groups[n].pins[0] && + pid < pctl->groups[n].pins[0] + + pctl->groups[n].npins) + return &pctl->groups[n]; + } + return NULL; +} + +static struct mvebu_pinctrl_group *mvebu_pinctrl_find_group_by_name( + struct mvebu_pinctrl *pctl, const char *name) +{ + unsigned n; + for (n = 0; n < pctl->num_groups; n++) { + if (strcmp(name, pctl->groups[n].name) == 0) + return &pctl->groups[n]; + } + return NULL; +} + +static struct mvebu_mpp_ctrl_setting *mvebu_pinctrl_find_setting_by_val( + struct mvebu_pinctrl *pctl, struct mvebu_pinctrl_group *grp, + unsigned long config) +{ + unsigned n; + for (n = 0; n < grp->num_settings; n++) { + if (config == grp->settings[n].val) { + if (!pctl->variant || (pctl->variant & + grp->settings[n].variant)) + return &grp->settings[n]; + } + } + return NULL; +} + +static struct mvebu_mpp_ctrl_setting *mvebu_pinctrl_find_setting_by_name( + struct mvebu_pinctrl *pctl, struct mvebu_pinctrl_group *grp, + const char *name) +{ + unsigned n; + for (n = 0; n < grp->num_settings; n++) { + if (strcmp(name, grp->settings[n].name) == 0) { + if (!pctl->variant || (pctl->variant & + grp->settings[n].variant)) + return &grp->settings[n]; + } + } + return NULL; +} + +static struct mvebu_mpp_ctrl_setting *mvebu_pinctrl_find_gpio_setting( + struct mvebu_pinctrl *pctl, struct mvebu_pinctrl_group *grp) +{ + unsigned n; + for (n = 0; n < grp->num_settings; n++) { + if (grp->settings[n].flags & + (MVEBU_SETTING_GPO | MVEBU_SETTING_GPI)) { + if (!pctl->variant || (pctl->variant & + grp->settings[n].variant)) + return &grp->settings[n]; + } + } + return NULL; +} + +static struct mvebu_pinctrl_function *mvebu_pinctrl_find_function_by_name( + struct mvebu_pinctrl *pctl, const char *name) +{ + unsigned n; + for (n = 0; n < pctl->num_functions; n++) { + if (strcmp(name, pctl->functions[n].name) == 0) + return &pctl->functions[n]; + } + return NULL; +} + +/* + * Common mpp pin configuration registers on MVEBU are + * registers of eight 4-bit values for each mpp setting. + * Register offset and bit mask are calculated accordingly below. + */ +static int mvebu_common_mpp_get(struct mvebu_pinctrl *pctl, + struct mvebu_pinctrl_group *grp, + unsigned long *config) +{ + unsigned pin = grp->gid; + unsigned off = (pin / MPPS_PER_REG) * MPP_BITS; + unsigned shift = (pin % MPPS_PER_REG) * MPP_BITS; + + *config = readl(pctl->base + off); + *config >>= shift; + *config &= MPP_MASK; + + return 0; +} + +static int mvebu_common_mpp_set(struct mvebu_pinctrl *pctl, + struct mvebu_pinctrl_group *grp, + unsigned long config) +{ + unsigned pin = grp->gid; + unsigned off = (pin / MPPS_PER_REG) * MPP_BITS; + unsigned shift = (pin % MPPS_PER_REG) * MPP_BITS; + unsigned long reg; + + reg = readl(pctl->base + off); + reg &= ~(MPP_MASK << shift); + reg |= (config << shift); + writel(reg, pctl->base + off); + + return 0; +} + +static int mvebu_pinconf_group_get(struct pinctrl_dev *pctldev, + unsigned gid, unsigned long *config) +{ + struct mvebu_pinctrl *pctl = pinctrl_dev_get_drvdata(pctldev); + struct mvebu_pinctrl_group *grp = &pctl->groups[gid]; + + if (!grp->ctrl) + return -EINVAL; + + if (grp->ctrl->mpp_get) + return grp->ctrl->mpp_get(grp->ctrl, config); + + return mvebu_common_mpp_get(pctl, grp, config); +} + +static int mvebu_pinconf_group_set(struct pinctrl_dev *pctldev, + unsigned gid, unsigned long config) +{ + struct mvebu_pinctrl *pctl = pinctrl_dev_get_drvdata(pctldev); + struct mvebu_pinctrl_group *grp = &pctl->groups[gid]; + + if (!grp->ctrl) + return -EINVAL; + + if (grp->ctrl->mpp_set) + return grp->ctrl->mpp_set(grp->ctrl, config); + + return mvebu_common_mpp_set(pctl, grp, config); +} + +static void mvebu_pinconf_group_dbg_show(struct pinctrl_dev *pctldev, + struct seq_file *s, unsigned gid) +{ + struct mvebu_pinctrl *pctl = pinctrl_dev_get_drvdata(pctldev); + struct mvebu_pinctrl_group *grp = &pctl->groups[gid]; + struct mvebu_mpp_ctrl_setting *curr; + unsigned long config; + unsigned n; + + if (mvebu_pinconf_group_get(pctldev, gid, &config)) + return; + + curr = mvebu_pinctrl_find_setting_by_val(pctl, grp, config); + + if (curr) { + seq_printf(s, "current: %s", curr->name); + if (curr->subname) + seq_printf(s, "(%s)", curr->subname); + if (curr->flags & (MVEBU_SETTING_GPO | MVEBU_SETTING_GPI)) { + seq_printf(s, "("); + if (curr->flags & MVEBU_SETTING_GPI) + seq_printf(s, "i"); + if (curr->flags & MVEBU_SETTING_GPO) + seq_printf(s, "o"); + seq_printf(s, ")"); + } + } else + seq_printf(s, "current: UNKNOWN"); + + if (grp->num_settings > 1) { + seq_printf(s, ", available = ["); + for (n = 0; n < grp->num_settings; n++) { + if (curr == &grp->settings[n]) + continue; + + /* skip unsupported settings for this variant */ + if (pctl->variant && + !(pctl->variant & grp->settings[n].variant)) + continue; + + seq_printf(s, " %s", grp->settings[n].name); + if (grp->settings[n].subname) + seq_printf(s, "(%s)", grp->settings[n].subname); + if (grp->settings[n].flags & + (MVEBU_SETTING_GPO | MVEBU_SETTING_GPI)) { + seq_printf(s, "("); + if (grp->settings[n].flags & MVEBU_SETTING_GPI) + seq_printf(s, "i"); + if (grp->settings[n].flags & MVEBU_SETTING_GPO) + seq_printf(s, "o"); + seq_printf(s, ")"); + } + } + seq_printf(s, " ]"); + } + return; +} + +static struct pinconf_ops mvebu_pinconf_ops = { + .pin_config_group_get = mvebu_pinconf_group_get, + .pin_config_group_set = mvebu_pinconf_group_set, + .pin_config_group_dbg_show = mvebu_pinconf_group_dbg_show, +}; + +static int mvebu_pinmux_get_funcs_count(struct pinctrl_dev *pctldev) +{ + struct mvebu_pinctrl *pctl = pinctrl_dev_get_drvdata(pctldev); + + return pctl->num_functions; +} + +static const char *mvebu_pinmux_get_func_name(struct pinctrl_dev *pctldev, + unsigned fid) +{ + struct mvebu_pinctrl *pctl = pinctrl_dev_get_drvdata(pctldev); + + return pctl->functions[fid].name; +} + +static int mvebu_pinmux_get_groups(struct pinctrl_dev *pctldev, unsigned fid, + const char * const **groups, + unsigned * const num_groups) +{ + struct mvebu_pinctrl *pctl = pinctrl_dev_get_drvdata(pctldev); + + *groups = pctl->functions[fid].groups; + *num_groups = pctl->functions[fid].num_groups; + return 0; +} + +static int mvebu_pinmux_enable(struct pinctrl_dev *pctldev, unsigned fid, + unsigned gid) +{ + struct mvebu_pinctrl *pctl = pinctrl_dev_get_drvdata(pctldev); + struct mvebu_pinctrl_function *func = &pctl->functions[fid]; + struct mvebu_pinctrl_group *grp = &pctl->groups[gid]; + struct mvebu_mpp_ctrl_setting *setting; + int ret; + + setting = mvebu_pinctrl_find_setting_by_name(pctl, grp, + func->name); + if (!setting) { + dev_err(pctl->dev, + "unable to find setting %s in group %s\n", + func->name, func->groups[gid]); + return -EINVAL; + } + + ret = mvebu_pinconf_group_set(pctldev, grp->gid, setting->val); + if (ret) { + dev_err(pctl->dev, "cannot set group %s to %s\n", + func->groups[gid], func->name); + return ret; + } + + return 0; +} + +static int mvebu_pinmux_gpio_request_enable(struct pinctrl_dev *pctldev, + struct pinctrl_gpio_range *range, unsigned offset) +{ + struct mvebu_pinctrl *pctl = pinctrl_dev_get_drvdata(pctldev); + struct mvebu_pinctrl_group *grp; + struct mvebu_mpp_ctrl_setting *setting; + + grp = mvebu_pinctrl_find_group_by_pid(pctl, offset); + if (!grp) + return -EINVAL; + + if (grp->ctrl->mpp_gpio_req) + return grp->ctrl->mpp_gpio_req(grp->ctrl, offset); + + setting = mvebu_pinctrl_find_gpio_setting(pctl, grp); + if (!setting) + return -ENOTSUPP; + + return mvebu_pinconf_group_set(pctldev, grp->gid, setting->val); +} + +static int mvebu_pinmux_gpio_set_direction(struct pinctrl_dev *pctldev, + struct pinctrl_gpio_range *range, unsigned offset, bool input) +{ + struct mvebu_pinctrl *pctl = pinctrl_dev_get_drvdata(pctldev); + struct mvebu_pinctrl_group *grp; + struct mvebu_mpp_ctrl_setting *setting; + + grp = mvebu_pinctrl_find_group_by_pid(pctl, offset); + if (!grp) + return -EINVAL; + + if (grp->ctrl->mpp_gpio_dir) + return grp->ctrl->mpp_gpio_dir(grp->ctrl, offset, input); + + setting = mvebu_pinctrl_find_gpio_setting(pctl, grp); + if (!setting) + return -ENOTSUPP; + + if ((input && (setting->flags & MVEBU_SETTING_GPI)) || + (!input && (setting->flags & MVEBU_SETTING_GPO))) + return 0; + + return -ENOTSUPP; +} + +static struct pinmux_ops mvebu_pinmux_ops = { + .get_functions_count = mvebu_pinmux_get_funcs_count, + .get_function_name = mvebu_pinmux_get_func_name, + .get_function_groups = mvebu_pinmux_get_groups, + .gpio_request_enable = mvebu_pinmux_gpio_request_enable, + .gpio_set_direction = mvebu_pinmux_gpio_set_direction, + .enable = mvebu_pinmux_enable, +}; + +static int mvebu_pinctrl_get_groups_count(struct pinctrl_dev *pctldev) +{ + struct mvebu_pinctrl *pctl = pinctrl_dev_get_drvdata(pctldev); + return pctl->num_groups; +} + +static const char *mvebu_pinctrl_get_group_name(struct pinctrl_dev *pctldev, + unsigned gid) +{ + struct mvebu_pinctrl *pctl = pinctrl_dev_get_drvdata(pctldev); + return pctl->groups[gid].name; +} + +static int mvebu_pinctrl_get_group_pins(struct pinctrl_dev *pctldev, + unsigned gid, const unsigned **pins, + unsigned *num_pins) +{ + struct mvebu_pinctrl *pctl = pinctrl_dev_get_drvdata(pctldev); + *pins = pctl->groups[gid].pins; + *num_pins = pctl->groups[gid].npins; + return 0; +} + +static int mvebu_pinctrl_dt_node_to_map(struct pinctrl_dev *pctldev, + struct device_node *np, + struct pinctrl_map **map, + unsigned *num_maps) +{ + struct mvebu_pinctrl *pctl = pinctrl_dev_get_drvdata(pctldev); + struct property *prop; + const char *function; + const char *group; + int ret, nmaps, n; + + *map = NULL; + *num_maps = 0; + + ret = of_property_read_string(np, "marvell,function", &function); + if (ret) { + dev_err(pctl->dev, + "missing marvell,function in node %s\n", np->name); + return 0; + } + + nmaps = of_property_count_strings(np, "marvell,pins"); + if (nmaps < 0) { + dev_err(pctl->dev, + "missing marvell,pins in node %s\n", np->name); + return 0; + } + + *map = kmalloc(nmaps * sizeof(struct pinctrl_map), GFP_KERNEL); + if (map == NULL) { + dev_err(pctl->dev, + "cannot allocate pinctrl_map memory for %s\n", + np->name); + return -ENOMEM; + } + + n = 0; + of_property_for_each_string(np, "marvell,pins", prop, group) { + struct mvebu_pinctrl_group *grp = + mvebu_pinctrl_find_group_by_name(pctl, group); + + if (!grp) { + dev_err(pctl->dev, "unknown pin %s", group); + continue; + } + + if (!mvebu_pinctrl_find_setting_by_name(pctl, grp, function)) { + dev_err(pctl->dev, "unsupported function %s on pin %s", + function, group); + continue; + } + + (*map)[n].type = PIN_MAP_TYPE_MUX_GROUP; + (*map)[n].data.mux.group = group; + (*map)[n].data.mux.function = function; + n++; + } + + *num_maps = nmaps; + + return 0; +} + +static void mvebu_pinctrl_dt_free_map(struct pinctrl_dev *pctldev, + struct pinctrl_map *map, unsigned num_maps) +{ + kfree(map); +} + +static struct pinctrl_ops mvebu_pinctrl_ops = { + .get_groups_count = mvebu_pinctrl_get_groups_count, + .get_group_name = mvebu_pinctrl_get_group_name, + .get_group_pins = mvebu_pinctrl_get_group_pins, + .dt_node_to_map = mvebu_pinctrl_dt_node_to_map, + .dt_free_map = mvebu_pinctrl_dt_free_map, +}; + +static int __devinit _add_function(struct mvebu_pinctrl_function *funcs, + const char *name) +{ + while (funcs->num_groups) { + /* function already there */ + if (strcmp(funcs->name, name) == 0) { + funcs->num_groups++; + return -EEXIST; + } + funcs++; + } + funcs->name = name; + funcs->num_groups = 1; + return 0; +} + +static int __devinit mvebu_pinctrl_build_functions(struct platform_device *pdev, + struct mvebu_pinctrl *pctl) +{ + struct mvebu_pinctrl_function *funcs; + int num = 0; + int n, s; + + /* we allocate functions for number of pins and hope + * there are less unique functions than pins available */ + funcs = devm_kzalloc(&pdev->dev, pctl->desc.npins * + sizeof(struct mvebu_pinctrl_function), GFP_KERNEL); + if (!funcs) + return -ENOMEM; + + for (n = 0; n < pctl->num_groups; n++) { + struct mvebu_pinctrl_group *grp = &pctl->groups[n]; + for (s = 0; s < grp->num_settings; s++) { + /* skip unsupported settings on this variant */ + if (pctl->variant && + !(pctl->variant & grp->settings[s].variant)) + continue; + + /* check for unique functions and count groups */ + if (_add_function(funcs, grp->settings[s].name)) + continue; + + num++; + } + } + + /* with the number of unique functions and it's groups known, + reallocate functions and assign group names */ + funcs = krealloc(funcs, num * sizeof(struct mvebu_pinctrl_function), + GFP_KERNEL); + if (!funcs) + return -ENOMEM; + + pctl->num_functions = num; + pctl->functions = funcs; + + for (n = 0; n < pctl->num_groups; n++) { + struct mvebu_pinctrl_group *grp = &pctl->groups[n]; + for (s = 0; s < grp->num_settings; s++) { + struct mvebu_pinctrl_function *f; + const char **groups; + + /* skip unsupported settings on this variant */ + if (pctl->variant && + !(pctl->variant & grp->settings[s].variant)) + continue; + + f = mvebu_pinctrl_find_function_by_name(pctl, + grp->settings[s].name); + + /* allocate group name array if not done already */ + if (!f->groups) { + f->groups = devm_kzalloc(&pdev->dev, + f->num_groups * sizeof(char *), + GFP_KERNEL); + if (!f->groups) + return -ENOMEM; + } + + /* find next free group name and assign current name */ + groups = f->groups; + while (*groups) + groups++; + *groups = grp->name; + } + } + + return 0; +} + +int __devinit mvebu_pinctrl_probe(struct platform_device *pdev) +{ + struct mvebu_pinctrl_soc_info *soc = dev_get_platdata(&pdev->dev); + struct device_node *np = pdev->dev.of_node; + struct mvebu_pinctrl *pctl; + void __iomem *base; + struct pinctrl_pin_desc *pdesc; + unsigned gid, n, k; + int ret; + + if (!soc || !soc->controls || !soc->modes) { + dev_err(&pdev->dev, "wrong pinctrl soc info\n"); + return -EINVAL; + } + + base = of_iomap(np, 0); + if (!base) { + dev_err(&pdev->dev, "unable to get base address\n"); + return -ENODEV; + } + + pctl = devm_kzalloc(&pdev->dev, sizeof(struct mvebu_pinctrl), + GFP_KERNEL); + if (!pctl) { + dev_err(&pdev->dev, "unable to alloc driver\n"); + return -ENOMEM; + } + + pctl->desc.name = dev_name(&pdev->dev); + pctl->desc.owner = THIS_MODULE; + pctl->desc.pctlops = &mvebu_pinctrl_ops; + pctl->desc.pmxops = &mvebu_pinmux_ops; + pctl->desc.confops = &mvebu_pinconf_ops; + pctl->variant = soc->variant; + pctl->base = base; + pctl->dev = &pdev->dev; + platform_set_drvdata(pdev, pctl); + + /* count controls and create names for mvebu generic + register controls; also does sanity checks */ + pctl->num_groups = 0; + pctl->desc.npins = 0; + for (n = 0; n < soc->ncontrols; n++) { + struct mvebu_mpp_ctrl *ctrl = &soc->controls[n]; + char *names; + + pctl->desc.npins += ctrl->npins; + /* initial control pins */ + for (k = 0; k < ctrl->npins; k++) + ctrl->pins[k] = ctrl->pid + k; + + /* special soc specific control */ + if (ctrl->mpp_get || ctrl->mpp_set) { + if (!ctrl->name || !ctrl->mpp_set || !ctrl->mpp_set) { + dev_err(&pdev->dev, "wrong soc control info\n"); + return -EINVAL; + } + pctl->num_groups += 1; + continue; + } + + /* generic mvebu register control */ + names = devm_kzalloc(&pdev->dev, ctrl->npins * 8, GFP_KERNEL); + if (!names) { + dev_err(&pdev->dev, "failed to alloc mpp names\n"); + return -ENOMEM; + } + for (k = 0; k < ctrl->npins; k++) + sprintf(names + 8*k, "mpp%d", ctrl->pid+k); + ctrl->name = names; + pctl->num_groups += ctrl->npins; + } + + pdesc = devm_kzalloc(&pdev->dev, pctl->desc.npins * + sizeof(struct pinctrl_pin_desc), GFP_KERNEL); + if (!pdesc) { + dev_err(&pdev->dev, "failed to alloc pinctrl pins\n"); + return -ENOMEM; + } + + for (n = 0; n < pctl->desc.npins; n++) + pdesc[n].number = n; + pctl->desc.pins = pdesc; + + pctl->groups = devm_kzalloc(&pdev->dev, pctl->num_groups * + sizeof(struct mvebu_pinctrl_group), GFP_KERNEL); + if (!pctl->groups) { + dev_err(&pdev->dev, "failed to alloc pinctrl groups\n"); + return -ENOMEM; + } + + /* assign mpp controls to groups */ + gid = 0; + for (n = 0; n < soc->ncontrols; n++) { + struct mvebu_mpp_ctrl *ctrl = &soc->controls[n]; + pctl->groups[gid].gid = gid; + pctl->groups[gid].ctrl = ctrl; + pctl->groups[gid].name = ctrl->name; + pctl->groups[gid].pins = ctrl->pins; + pctl->groups[gid].npins = ctrl->npins; + + /* generic mvebu register control maps to a number of groups */ + if (!ctrl->mpp_get && !ctrl->mpp_set) { + pctl->groups[gid].npins = 1; + + for (k = 1; k < ctrl->npins; k++) { + gid++; + pctl->groups[gid].gid = gid; + pctl->groups[gid].ctrl = ctrl; + pctl->groups[gid].name = &ctrl->name[8*k]; + pctl->groups[gid].pins = &ctrl->pins[k]; + pctl->groups[gid].npins = 1; + } + } + gid++; + } + + /* assign mpp modes to groups */ + for (n = 0; n < soc->nmodes; n++) { + struct mvebu_mpp_mode *mode = &soc->modes[n]; + struct mvebu_pinctrl_group *grp = + mvebu_pinctrl_find_group_by_pid(pctl, mode->pid); + unsigned num_settings; + + if (!grp) { + dev_warn(&pdev->dev, "unknown pinctrl group %d\n", + mode->pid); + continue; + } + + for (num_settings = 0; ;) { + struct mvebu_mpp_ctrl_setting *set = + &mode->settings[num_settings]; + + if (!set->name) + break; + num_settings++; + + /* skip unsupported settings for this variant */ + if (pctl->variant && !(pctl->variant & set->variant)) + continue; + + /* find gpio/gpo/gpi settings */ + if (strcmp(set->name, "gpio") == 0) + set->flags = MVEBU_SETTING_GPI | + MVEBU_SETTING_GPO; + else if (strcmp(set->name, "gpo") == 0) + set->flags = MVEBU_SETTING_GPO; + else if (strcmp(set->name, "gpi") == 0) + set->flags = MVEBU_SETTING_GPI; + } + + grp->settings = mode->settings; + grp->num_settings = num_settings; + } + + ret = mvebu_pinctrl_build_functions(pdev, pctl); + if (ret) { + dev_err(&pdev->dev, "unable to build functions\n"); + return ret; + } + + pctl->pctldev = pinctrl_register(&pctl->desc, &pdev->dev, pctl); + if (!pctl->pctldev) { + dev_err(&pdev->dev, "unable to register pinctrl driver\n"); + return -EINVAL; + } + + dev_info(&pdev->dev, "registered pinctrl driver\n"); + + /* register gpio ranges */ + for (n = 0; n < soc->ngpioranges; n++) + pinctrl_add_gpio_range(pctl->pctldev, &soc->gpioranges[n]); + + return 0; +} + +int __devexit mvebu_pinctrl_remove(struct platform_device *pdev) +{ + struct mvebu_pinctrl *pctl = platform_get_drvdata(pdev); + pinctrl_unregister(pctl->pctldev); + return 0; +} |