forked from Qortal/Brooklyn
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
425 lines
11 KiB
425 lines
11 KiB
// SPDX-License-Identifier: GPL-2.0-only |
|
/* |
|
* Device tree integration for the pin control subsystem |
|
* |
|
* Copyright (C) 2012 NVIDIA CORPORATION. All rights reserved. |
|
*/ |
|
|
|
#include <linux/device.h> |
|
#include <linux/of.h> |
|
#include <linux/pinctrl/pinctrl.h> |
|
#include <linux/slab.h> |
|
|
|
#include "core.h" |
|
#include "devicetree.h" |
|
|
|
/** |
|
* struct pinctrl_dt_map - mapping table chunk parsed from device tree |
|
* @node: list node for struct pinctrl's @dt_maps field |
|
* @pctldev: the pin controller that allocated this struct, and will free it |
|
* @map: the mapping table entries |
|
* @num_maps: number of mapping table entries |
|
*/ |
|
struct pinctrl_dt_map { |
|
struct list_head node; |
|
struct pinctrl_dev *pctldev; |
|
struct pinctrl_map *map; |
|
unsigned num_maps; |
|
}; |
|
|
|
static void dt_free_map(struct pinctrl_dev *pctldev, |
|
struct pinctrl_map *map, unsigned num_maps) |
|
{ |
|
int i; |
|
|
|
for (i = 0; i < num_maps; ++i) { |
|
kfree_const(map[i].dev_name); |
|
map[i].dev_name = NULL; |
|
} |
|
|
|
if (pctldev) { |
|
const struct pinctrl_ops *ops = pctldev->desc->pctlops; |
|
if (ops->dt_free_map) |
|
ops->dt_free_map(pctldev, map, num_maps); |
|
} else { |
|
/* There is no pctldev for PIN_MAP_TYPE_DUMMY_STATE */ |
|
kfree(map); |
|
} |
|
} |
|
|
|
void pinctrl_dt_free_maps(struct pinctrl *p) |
|
{ |
|
struct pinctrl_dt_map *dt_map, *n1; |
|
|
|
list_for_each_entry_safe(dt_map, n1, &p->dt_maps, node) { |
|
pinctrl_unregister_mappings(dt_map->map); |
|
list_del(&dt_map->node); |
|
dt_free_map(dt_map->pctldev, dt_map->map, |
|
dt_map->num_maps); |
|
kfree(dt_map); |
|
} |
|
|
|
of_node_put(p->dev->of_node); |
|
} |
|
|
|
static int dt_remember_or_free_map(struct pinctrl *p, const char *statename, |
|
struct pinctrl_dev *pctldev, |
|
struct pinctrl_map *map, unsigned num_maps) |
|
{ |
|
int i; |
|
struct pinctrl_dt_map *dt_map; |
|
|
|
/* Initialize common mapping table entry fields */ |
|
for (i = 0; i < num_maps; i++) { |
|
const char *devname; |
|
|
|
devname = kstrdup_const(dev_name(p->dev), GFP_KERNEL); |
|
if (!devname) |
|
goto err_free_map; |
|
|
|
map[i].dev_name = devname; |
|
map[i].name = statename; |
|
if (pctldev) |
|
map[i].ctrl_dev_name = dev_name(pctldev->dev); |
|
} |
|
|
|
/* Remember the converted mapping table entries */ |
|
dt_map = kzalloc(sizeof(*dt_map), GFP_KERNEL); |
|
if (!dt_map) |
|
goto err_free_map; |
|
|
|
dt_map->pctldev = pctldev; |
|
dt_map->map = map; |
|
dt_map->num_maps = num_maps; |
|
list_add_tail(&dt_map->node, &p->dt_maps); |
|
|
|
return pinctrl_register_mappings(map, num_maps); |
|
|
|
err_free_map: |
|
dt_free_map(pctldev, map, num_maps); |
|
return -ENOMEM; |
|
} |
|
|
|
struct pinctrl_dev *of_pinctrl_get(struct device_node *np) |
|
{ |
|
return get_pinctrl_dev_from_of_node(np); |
|
} |
|
EXPORT_SYMBOL_GPL(of_pinctrl_get); |
|
|
|
static int dt_to_map_one_config(struct pinctrl *p, |
|
struct pinctrl_dev *hog_pctldev, |
|
const char *statename, |
|
struct device_node *np_config) |
|
{ |
|
struct pinctrl_dev *pctldev = NULL; |
|
struct device_node *np_pctldev; |
|
const struct pinctrl_ops *ops; |
|
int ret; |
|
struct pinctrl_map *map; |
|
unsigned num_maps; |
|
bool allow_default = false; |
|
|
|
/* Find the pin controller containing np_config */ |
|
np_pctldev = of_node_get(np_config); |
|
for (;;) { |
|
if (!allow_default) |
|
allow_default = of_property_read_bool(np_pctldev, |
|
"pinctrl-use-default"); |
|
|
|
np_pctldev = of_get_next_parent(np_pctldev); |
|
if (!np_pctldev || of_node_is_root(np_pctldev)) { |
|
of_node_put(np_pctldev); |
|
ret = driver_deferred_probe_check_state(p->dev); |
|
/* keep deferring if modules are enabled */ |
|
if (IS_ENABLED(CONFIG_MODULES) && !allow_default && ret < 0) |
|
ret = -EPROBE_DEFER; |
|
return ret; |
|
} |
|
/* If we're creating a hog we can use the passed pctldev */ |
|
if (hog_pctldev && (np_pctldev == p->dev->of_node)) { |
|
pctldev = hog_pctldev; |
|
break; |
|
} |
|
pctldev = get_pinctrl_dev_from_of_node(np_pctldev); |
|
if (pctldev) |
|
break; |
|
/* Do not defer probing of hogs (circular loop) */ |
|
if (np_pctldev == p->dev->of_node) { |
|
of_node_put(np_pctldev); |
|
return -ENODEV; |
|
} |
|
} |
|
of_node_put(np_pctldev); |
|
|
|
/* |
|
* Call pinctrl driver to parse device tree node, and |
|
* generate mapping table entries |
|
*/ |
|
ops = pctldev->desc->pctlops; |
|
if (!ops->dt_node_to_map) { |
|
dev_err(p->dev, "pctldev %s doesn't support DT\n", |
|
dev_name(pctldev->dev)); |
|
return -ENODEV; |
|
} |
|
ret = ops->dt_node_to_map(pctldev, np_config, &map, &num_maps); |
|
if (ret < 0) |
|
return ret; |
|
else if (num_maps == 0) { |
|
/* |
|
* If we have no valid maps (maybe caused by empty pinctrl node |
|
* or typing error) ther is no need remember this, so just |
|
* return. |
|
*/ |
|
dev_info(p->dev, |
|
"there is not valid maps for state %s\n", statename); |
|
return 0; |
|
} |
|
|
|
/* Stash the mapping table chunk away for later use */ |
|
return dt_remember_or_free_map(p, statename, pctldev, map, num_maps); |
|
} |
|
|
|
static int dt_remember_dummy_state(struct pinctrl *p, const char *statename) |
|
{ |
|
struct pinctrl_map *map; |
|
|
|
map = kzalloc(sizeof(*map), GFP_KERNEL); |
|
if (!map) |
|
return -ENOMEM; |
|
|
|
/* There is no pctldev for PIN_MAP_TYPE_DUMMY_STATE */ |
|
map->type = PIN_MAP_TYPE_DUMMY_STATE; |
|
|
|
return dt_remember_or_free_map(p, statename, NULL, map, 1); |
|
} |
|
|
|
int pinctrl_dt_to_map(struct pinctrl *p, struct pinctrl_dev *pctldev) |
|
{ |
|
struct device_node *np = p->dev->of_node; |
|
int state, ret; |
|
char *propname; |
|
struct property *prop; |
|
const char *statename; |
|
const __be32 *list; |
|
int size, config; |
|
phandle phandle; |
|
struct device_node *np_config; |
|
|
|
/* CONFIG_OF enabled, p->dev not instantiated from DT */ |
|
if (!np) { |
|
if (of_have_populated_dt()) |
|
dev_dbg(p->dev, |
|
"no of_node; not parsing pinctrl DT\n"); |
|
return 0; |
|
} |
|
|
|
/* We may store pointers to property names within the node */ |
|
of_node_get(np); |
|
|
|
/* For each defined state ID */ |
|
for (state = 0; ; state++) { |
|
/* Retrieve the pinctrl-* property */ |
|
propname = kasprintf(GFP_KERNEL, "pinctrl-%d", state); |
|
prop = of_find_property(np, propname, &size); |
|
kfree(propname); |
|
if (!prop) { |
|
if (state == 0) { |
|
of_node_put(np); |
|
return -ENODEV; |
|
} |
|
break; |
|
} |
|
list = prop->value; |
|
size /= sizeof(*list); |
|
|
|
/* Determine whether pinctrl-names property names the state */ |
|
ret = of_property_read_string_index(np, "pinctrl-names", |
|
state, &statename); |
|
/* |
|
* If not, statename is just the integer state ID. But rather |
|
* than dynamically allocate it and have to free it later, |
|
* just point part way into the property name for the string. |
|
*/ |
|
if (ret < 0) |
|
statename = prop->name + strlen("pinctrl-"); |
|
|
|
/* For every referenced pin configuration node in it */ |
|
for (config = 0; config < size; config++) { |
|
phandle = be32_to_cpup(list++); |
|
|
|
/* Look up the pin configuration node */ |
|
np_config = of_find_node_by_phandle(phandle); |
|
if (!np_config) { |
|
dev_err(p->dev, |
|
"prop %s index %i invalid phandle\n", |
|
prop->name, config); |
|
ret = -EINVAL; |
|
goto err; |
|
} |
|
|
|
/* Parse the node */ |
|
ret = dt_to_map_one_config(p, pctldev, statename, |
|
np_config); |
|
of_node_put(np_config); |
|
if (ret < 0) |
|
goto err; |
|
} |
|
|
|
/* No entries in DT? Generate a dummy state table entry */ |
|
if (!size) { |
|
ret = dt_remember_dummy_state(p, statename); |
|
if (ret < 0) |
|
goto err; |
|
} |
|
} |
|
|
|
return 0; |
|
|
|
err: |
|
pinctrl_dt_free_maps(p); |
|
return ret; |
|
} |
|
|
|
/* |
|
* For pinctrl binding, typically #pinctrl-cells is for the pin controller |
|
* device, so either parent or grandparent. See pinctrl-bindings.txt. |
|
*/ |
|
static int pinctrl_find_cells_size(const struct device_node *np) |
|
{ |
|
const char *cells_name = "#pinctrl-cells"; |
|
int cells_size, error; |
|
|
|
error = of_property_read_u32(np->parent, cells_name, &cells_size); |
|
if (error) { |
|
error = of_property_read_u32(np->parent->parent, |
|
cells_name, &cells_size); |
|
if (error) |
|
return -ENOENT; |
|
} |
|
|
|
return cells_size; |
|
} |
|
|
|
/** |
|
* pinctrl_get_list_and_count - Gets the list and it's cell size and number |
|
* @np: pointer to device node with the property |
|
* @list_name: property that contains the list |
|
* @list: pointer for the list found |
|
* @cells_size: pointer for the cell size found |
|
* @nr_elements: pointer for the number of elements found |
|
* |
|
* Typically np is a single pinctrl entry containing the list. |
|
*/ |
|
static int pinctrl_get_list_and_count(const struct device_node *np, |
|
const char *list_name, |
|
const __be32 **list, |
|
int *cells_size, |
|
int *nr_elements) |
|
{ |
|
int size; |
|
|
|
*cells_size = 0; |
|
*nr_elements = 0; |
|
|
|
*list = of_get_property(np, list_name, &size); |
|
if (!*list) |
|
return -ENOENT; |
|
|
|
*cells_size = pinctrl_find_cells_size(np); |
|
if (*cells_size < 0) |
|
return -ENOENT; |
|
|
|
/* First element is always the index within the pinctrl device */ |
|
*nr_elements = (size / sizeof(**list)) / (*cells_size + 1); |
|
|
|
return 0; |
|
} |
|
|
|
/** |
|
* pinctrl_count_index_with_args - Count number of elements in a pinctrl entry |
|
* @np: pointer to device node with the property |
|
* @list_name: property that contains the list |
|
* |
|
* Counts the number of elements in a pinctrl array consisting of an index |
|
* within the controller and a number of u32 entries specified for each |
|
* entry. Note that device_node is always for the parent pin controller device. |
|
*/ |
|
int pinctrl_count_index_with_args(const struct device_node *np, |
|
const char *list_name) |
|
{ |
|
const __be32 *list; |
|
int size, nr_cells, error; |
|
|
|
error = pinctrl_get_list_and_count(np, list_name, &list, |
|
&nr_cells, &size); |
|
if (error) |
|
return error; |
|
|
|
return size; |
|
} |
|
EXPORT_SYMBOL_GPL(pinctrl_count_index_with_args); |
|
|
|
/** |
|
* pinctrl_copy_args - Populates of_phandle_args based on index |
|
* @np: pointer to device node with the property |
|
* @list: pointer to a list with the elements |
|
* @index: entry within the list of elements |
|
* @nr_cells: number of cells in the list |
|
* @nr_elem: number of elements for each entry in the list |
|
* @out_args: returned values |
|
* |
|
* Populates the of_phandle_args based on the index in the list. |
|
*/ |
|
static int pinctrl_copy_args(const struct device_node *np, |
|
const __be32 *list, |
|
int index, int nr_cells, int nr_elem, |
|
struct of_phandle_args *out_args) |
|
{ |
|
int i; |
|
|
|
memset(out_args, 0, sizeof(*out_args)); |
|
out_args->np = (struct device_node *)np; |
|
out_args->args_count = nr_cells + 1; |
|
|
|
if (index >= nr_elem) |
|
return -EINVAL; |
|
|
|
list += index * (nr_cells + 1); |
|
|
|
for (i = 0; i < nr_cells + 1; i++) |
|
out_args->args[i] = be32_to_cpup(list++); |
|
|
|
return 0; |
|
} |
|
|
|
/** |
|
* pinctrl_parse_index_with_args - Find a node pointed by index in a list |
|
* @np: pointer to device node with the property |
|
* @list_name: property that contains the list |
|
* @index: index within the list |
|
* @out_args: entries in the list pointed by index |
|
* |
|
* Finds the selected element in a pinctrl array consisting of an index |
|
* within the controller and a number of u32 entries specified for each |
|
* entry. Note that device_node is always for the parent pin controller device. |
|
*/ |
|
int pinctrl_parse_index_with_args(const struct device_node *np, |
|
const char *list_name, int index, |
|
struct of_phandle_args *out_args) |
|
{ |
|
const __be32 *list; |
|
int nr_elem, nr_cells, error; |
|
|
|
error = pinctrl_get_list_and_count(np, list_name, &list, |
|
&nr_cells, &nr_elem); |
|
if (error || !nr_cells) |
|
return error; |
|
|
|
error = pinctrl_copy_args(np, list, index, nr_cells, nr_elem, |
|
out_args); |
|
if (error) |
|
return error; |
|
|
|
return 0; |
|
} |
|
EXPORT_SYMBOL_GPL(pinctrl_parse_index_with_args);
|
|
|