| // 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; |
| |
| 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 = kcalloc(max_functions, sizeof(*pctrl->functions), GFP_KERNEL); |
| 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++; |
| } |
| } |
| |
| pctrl->functions = krealloc(pctrl->functions, |
| pctrl->nfunctions * sizeof(*pctrl->functions), |
| GFP_KERNEL); |
| if (!pctrl->functions) |
| return -ENOMEM; |
| |
| /* 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 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); |
| } |