| // SPDX-License-Identifier: GPL-2.0 | |
| /* | |
| * Marvell Berlin SoC pinctrl core driver | |
| * | |
| * Copyright (C) 2014 Marvell Technology Group Ltd. | |
| * | |
| * Antoine Ténart <antoine.tenart@free-electrons.com> | |
| */ | |
| #include <linux/io.h> | |
| #include <linux/mfd/syscon.h> | |
| #include <linux/module.h> | |
| #include <linux/of.h> | |
| #include <linux/of_address.h> | |
| #include <linux/of_device.h> | |
| #include <linux/pinctrl/pinctrl.h> | |
| #include <linux/pinctrl/pinmux.h> | |
| #include <linux/platform_device.h> | |
| #include <linux/regmap.h> | |
| #include <linux/slab.h> | |
| #include "../core.h" | |
| #include "../pinctrl-utils.h" | |
| #include "berlin.h" | |
| struct berlin_pinctrl { | |
| struct regmap *regmap; | |
| struct device *dev; | |
| const struct berlin_pinctrl_desc *desc; | |
| struct pinfunction *functions; | |
| unsigned nfunctions; | |
| struct pinctrl_dev *pctrl_dev; | |
| }; | |
| static int berlin_pinctrl_get_group_count(struct pinctrl_dev *pctrl_dev) | |
| { | |
| struct berlin_pinctrl *pctrl = pinctrl_dev_get_drvdata(pctrl_dev); | |
| return pctrl->desc->ngroups; | |
| } | |
| static const char *berlin_pinctrl_get_group_name(struct pinctrl_dev *pctrl_dev, | |
| unsigned group) | |
| { | |
| struct berlin_pinctrl *pctrl = pinctrl_dev_get_drvdata(pctrl_dev); | |
| return pctrl->desc->groups[group].name; | |
| } | |
| static int berlin_pinctrl_dt_node_to_map(struct pinctrl_dev *pctrl_dev, | |
| struct device_node *node, | |
| struct pinctrl_map **map, | |
| unsigned *num_maps) | |
| { | |
| struct berlin_pinctrl *pctrl = pinctrl_dev_get_drvdata(pctrl_dev); | |
| struct property *prop; | |
| const char *function_name, *group_name; | |
| unsigned reserved_maps = 0; | |
| int ret, ngroups; | |
| *map = NULL; | |
| *num_maps = 0; | |
| ret = of_property_read_string(node, "function", &function_name); | |
| if (ret) { | |
| dev_err(pctrl->dev, | |
| "missing function property in node %pOFn\n", node); | |
| return -EINVAL; | |
| } | |
| ngroups = of_property_count_strings(node, "groups"); | |
| if (ngroups < 0) { | |
| dev_err(pctrl->dev, | |
| "missing groups property in node %pOFn\n", node); | |
| return -EINVAL; | |
| } | |
| ret = pinctrl_utils_reserve_map(pctrl_dev, map, &reserved_maps, | |
| num_maps, ngroups); | |
| if (ret) { | |
| dev_err(pctrl->dev, "can't reserve map: %d\n", ret); | |
| return ret; | |
| } | |
| of_property_for_each_string(node, "groups", prop, group_name) { | |
| ret = pinctrl_utils_add_map_mux(pctrl_dev, map, &reserved_maps, | |
| num_maps, group_name, | |
| function_name); | |
| if (ret) { | |
| dev_err(pctrl->dev, "can't add map: %d\n", ret); | |
| return ret; | |
| } | |
| } | |
| return 0; | |
| } | |
| static const struct pinctrl_ops berlin_pinctrl_ops = { | |
| .get_groups_count = berlin_pinctrl_get_group_count, | |
| .get_group_name = berlin_pinctrl_get_group_name, | |
| .dt_node_to_map = berlin_pinctrl_dt_node_to_map, | |
| .dt_free_map = pinctrl_utils_free_map, | |
| }; | |
| static int berlin_pinmux_get_functions_count(struct pinctrl_dev *pctrl_dev) | |
| { | |
| struct berlin_pinctrl *pctrl = pinctrl_dev_get_drvdata(pctrl_dev); | |
| return pctrl->nfunctions; | |
| } | |
| static const char *berlin_pinmux_get_function_name(struct pinctrl_dev *pctrl_dev, | |
| unsigned function) | |
| { | |
| struct berlin_pinctrl *pctrl = pinctrl_dev_get_drvdata(pctrl_dev); | |
| return pctrl->functions[function].name; | |
| } | |
| static int berlin_pinmux_get_function_groups(struct pinctrl_dev *pctrl_dev, | |
| unsigned function, | |
| const char * const **groups, | |
| unsigned * const ngroups) | |
| { | |
| struct berlin_pinctrl *pctrl = pinctrl_dev_get_drvdata(pctrl_dev); | |
| *groups = pctrl->functions[function].groups; | |
| *ngroups = pctrl->functions[function].ngroups; | |
| return 0; | |
| } | |
| static struct berlin_desc_function * | |
| berlin_pinctrl_find_function_by_name(struct berlin_pinctrl *pctrl, | |
| const struct berlin_desc_group *group, | |
| const char *fname) | |
| { | |
| struct berlin_desc_function *function = group->functions; | |
| while (function->name) { | |
| if (!strcmp(function->name, fname)) | |
| return function; | |
| function++; | |
| } | |
| return NULL; | |
| } | |
| static int berlin_pinmux_set(struct pinctrl_dev *pctrl_dev, | |
| unsigned function, | |
| unsigned group) | |
| { | |
| struct berlin_pinctrl *pctrl = pinctrl_dev_get_drvdata(pctrl_dev); | |
| const struct berlin_desc_group *group_desc = pctrl->desc->groups + group; | |
| struct pinfunction *func = pctrl->functions + function; | |
| struct berlin_desc_function *function_desc = | |
| berlin_pinctrl_find_function_by_name(pctrl, group_desc, | |
| func->name); | |
| u32 mask, val; | |
| if (!function_desc) | |
| return -EINVAL; | |
| mask = GENMASK(group_desc->lsb + group_desc->bit_width - 1, | |
| group_desc->lsb); | |
| val = function_desc->muxval << group_desc->lsb; | |
| regmap_update_bits(pctrl->regmap, group_desc->offset, mask, val); | |
| return 0; | |
| } | |
| static const struct pinmux_ops berlin_pinmux_ops = { | |
| .get_functions_count = &berlin_pinmux_get_functions_count, | |
| .get_function_name = &berlin_pinmux_get_function_name, | |
| .get_function_groups = &berlin_pinmux_get_function_groups, | |
| .set_mux = &berlin_pinmux_set, | |
| }; | |
| static int berlin_pinctrl_add_function(struct berlin_pinctrl *pctrl, | |
| const char *name) | |
| { | |
| struct pinfunction *function = pctrl->functions; | |
| while (function->name) { | |
| if (!strcmp(function->name, name)) { | |
| function->ngroups++; | |
| return -EEXIST; | |
| } | |
| function++; | |
| } | |
| function->name = name; | |
| function->ngroups = 1; | |
| pctrl->nfunctions++; | |
| return 0; | |
| } | |
| static int berlin_pinctrl_build_state(struct platform_device *pdev) | |
| { | |
| struct berlin_pinctrl *pctrl = platform_get_drvdata(pdev); | |
| const struct berlin_desc_group *desc_group; | |
| const struct berlin_desc_function *desc_function; | |
| int i, max_functions = 0; | |
| struct pinfunction *new_functions; | |
| pctrl->nfunctions = 0; | |
| for (i = 0; i < pctrl->desc->ngroups; i++) { | |
| desc_group = pctrl->desc->groups + i; | |
| /* compute the maximum number of functions a group can have */ | |
| max_functions += 1 << (desc_group->bit_width + 1); | |
| } | |
| /* we will reallocate later */ | |
| pctrl->functions = kzalloc_objs(*pctrl->functions, max_functions); | |
| if (!pctrl->functions) | |
| return -ENOMEM; | |
| /* register all functions */ | |
| for (i = 0; i < pctrl->desc->ngroups; i++) { | |
| desc_group = pctrl->desc->groups + i; | |
| desc_function = desc_group->functions; | |
| while (desc_function->name) { | |
| berlin_pinctrl_add_function(pctrl, desc_function->name); | |
| desc_function++; | |
| } | |
| } | |
| new_functions = krealloc(pctrl->functions, | |
| pctrl->nfunctions * sizeof(*pctrl->functions), | |
| GFP_KERNEL); | |
| if (!new_functions) { | |
| kfree(pctrl->functions); | |
| return -ENOMEM; | |
| } | |
| pctrl->functions = new_functions; | |
| /* map functions to theirs groups */ | |
| for (i = 0; i < pctrl->desc->ngroups; i++) { | |
| desc_group = pctrl->desc->groups + i; | |
| desc_function = desc_group->functions; | |
| while (desc_function->name) { | |
| struct pinfunction *function = pctrl->functions; | |
| const char **groups; | |
| bool found = false; | |
| while (function->name) { | |
| if (!strcmp(desc_function->name, function->name)) { | |
| found = true; | |
| break; | |
| } | |
| function++; | |
| } | |
| if (!found) { | |
| kfree(pctrl->functions); | |
| return -EINVAL; | |
| } | |
| if (!function->groups) { | |
| function->groups = | |
| devm_kcalloc(&pdev->dev, | |
| function->ngroups, | |
| sizeof(*function->groups), | |
| GFP_KERNEL); | |
| if (!function->groups) { | |
| kfree(pctrl->functions); | |
| return -ENOMEM; | |
| } | |
| } | |
| groups = (const char **)function->groups; | |
| while (*groups) | |
| groups++; | |
| *groups = desc_group->name; | |
| desc_function++; | |
| } | |
| } | |
| return 0; | |
| } | |
| static const struct pinctrl_desc berlin_pctrl_desc = { | |
| .name = "berlin-pinctrl", | |
| .pctlops = &berlin_pinctrl_ops, | |
| .pmxops = &berlin_pinmux_ops, | |
| .owner = THIS_MODULE, | |
| }; | |
| int berlin_pinctrl_probe_regmap(struct platform_device *pdev, | |
| const struct berlin_pinctrl_desc *desc, | |
| struct regmap *regmap) | |
| { | |
| struct device *dev = &pdev->dev; | |
| struct berlin_pinctrl *pctrl; | |
| int ret; | |
| pctrl = devm_kzalloc(dev, sizeof(*pctrl), GFP_KERNEL); | |
| if (!pctrl) | |
| return -ENOMEM; | |
| platform_set_drvdata(pdev, pctrl); | |
| pctrl->regmap = regmap; | |
| pctrl->dev = &pdev->dev; | |
| pctrl->desc = desc; | |
| ret = berlin_pinctrl_build_state(pdev); | |
| if (ret) { | |
| dev_err(dev, "cannot build driver state: %d\n", ret); | |
| return ret; | |
| } | |
| pctrl->pctrl_dev = devm_pinctrl_register(dev, &berlin_pctrl_desc, | |
| pctrl); | |
| if (IS_ERR(pctrl->pctrl_dev)) { | |
| dev_err(dev, "failed to register pinctrl driver\n"); | |
| return PTR_ERR(pctrl->pctrl_dev); | |
| } | |
| return 0; | |
| } | |
| int berlin_pinctrl_probe(struct platform_device *pdev, | |
| const struct berlin_pinctrl_desc *desc) | |
| { | |
| struct device *dev = &pdev->dev; | |
| struct device_node *parent_np = of_get_parent(dev->of_node); | |
| struct regmap *regmap = syscon_node_to_regmap(parent_np); | |
| of_node_put(parent_np); | |
| if (IS_ERR(regmap)) | |
| return PTR_ERR(regmap); | |
| return berlin_pinctrl_probe_regmap(pdev, desc, regmap); | |
| } |