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.
658 lines
16 KiB
658 lines
16 KiB
// SPDX-License-Identifier: GPL-2.0-or-later |
|
/* |
|
* Copyright (C) 2016 IBM Corp. |
|
*/ |
|
|
|
#include <linux/mfd/syscon.h> |
|
#include <linux/platform_device.h> |
|
#include <linux/slab.h> |
|
#include <linux/string.h> |
|
#include "../core.h" |
|
#include "pinctrl-aspeed.h" |
|
|
|
int aspeed_pinctrl_get_groups_count(struct pinctrl_dev *pctldev) |
|
{ |
|
struct aspeed_pinctrl_data *pdata = pinctrl_dev_get_drvdata(pctldev); |
|
|
|
return pdata->pinmux.ngroups; |
|
} |
|
|
|
const char *aspeed_pinctrl_get_group_name(struct pinctrl_dev *pctldev, |
|
unsigned int group) |
|
{ |
|
struct aspeed_pinctrl_data *pdata = pinctrl_dev_get_drvdata(pctldev); |
|
|
|
return pdata->pinmux.groups[group].name; |
|
} |
|
|
|
int aspeed_pinctrl_get_group_pins(struct pinctrl_dev *pctldev, |
|
unsigned int group, const unsigned int **pins, |
|
unsigned int *npins) |
|
{ |
|
struct aspeed_pinctrl_data *pdata = pinctrl_dev_get_drvdata(pctldev); |
|
|
|
*pins = &pdata->pinmux.groups[group].pins[0]; |
|
*npins = pdata->pinmux.groups[group].npins; |
|
|
|
return 0; |
|
} |
|
|
|
void aspeed_pinctrl_pin_dbg_show(struct pinctrl_dev *pctldev, |
|
struct seq_file *s, unsigned int offset) |
|
{ |
|
seq_printf(s, " %s", dev_name(pctldev->dev)); |
|
} |
|
|
|
int aspeed_pinmux_get_fn_count(struct pinctrl_dev *pctldev) |
|
{ |
|
struct aspeed_pinctrl_data *pdata = pinctrl_dev_get_drvdata(pctldev); |
|
|
|
return pdata->pinmux.nfunctions; |
|
} |
|
|
|
const char *aspeed_pinmux_get_fn_name(struct pinctrl_dev *pctldev, |
|
unsigned int function) |
|
{ |
|
struct aspeed_pinctrl_data *pdata = pinctrl_dev_get_drvdata(pctldev); |
|
|
|
return pdata->pinmux.functions[function].name; |
|
} |
|
|
|
int aspeed_pinmux_get_fn_groups(struct pinctrl_dev *pctldev, |
|
unsigned int function, |
|
const char * const **groups, |
|
unsigned int * const num_groups) |
|
{ |
|
struct aspeed_pinctrl_data *pdata = pinctrl_dev_get_drvdata(pctldev); |
|
|
|
*groups = pdata->pinmux.functions[function].groups; |
|
*num_groups = pdata->pinmux.functions[function].ngroups; |
|
|
|
return 0; |
|
} |
|
|
|
static int aspeed_sig_expr_enable(struct aspeed_pinmux_data *ctx, |
|
const struct aspeed_sig_expr *expr) |
|
{ |
|
int ret; |
|
|
|
pr_debug("Enabling signal %s for %s\n", expr->signal, |
|
expr->function); |
|
|
|
ret = aspeed_sig_expr_eval(ctx, expr, true); |
|
if (ret < 0) |
|
return ret; |
|
|
|
if (!ret) |
|
return aspeed_sig_expr_set(ctx, expr, true); |
|
|
|
return 0; |
|
} |
|
|
|
static int aspeed_sig_expr_disable(struct aspeed_pinmux_data *ctx, |
|
const struct aspeed_sig_expr *expr) |
|
{ |
|
int ret; |
|
|
|
pr_debug("Disabling signal %s for %s\n", expr->signal, |
|
expr->function); |
|
|
|
ret = aspeed_sig_expr_eval(ctx, expr, true); |
|
if (ret < 0) |
|
return ret; |
|
|
|
if (ret) |
|
return aspeed_sig_expr_set(ctx, expr, false); |
|
|
|
return 0; |
|
} |
|
|
|
/** |
|
* aspeed_disable_sig() - Disable a signal on a pin by disabling all provided |
|
* signal expressions. |
|
* |
|
* @ctx: The pinmux context |
|
* @exprs: The list of signal expressions (from a priority level on a pin) |
|
* |
|
* Return: 0 if all expressions are disabled, otherwise a negative error code |
|
*/ |
|
static int aspeed_disable_sig(struct aspeed_pinmux_data *ctx, |
|
const struct aspeed_sig_expr **exprs) |
|
{ |
|
int ret = 0; |
|
|
|
if (!exprs) |
|
return true; |
|
|
|
while (*exprs && !ret) { |
|
ret = aspeed_sig_expr_disable(ctx, *exprs); |
|
exprs++; |
|
} |
|
|
|
return ret; |
|
} |
|
|
|
/** |
|
* aspeed_find_expr_by_name - Search for the signal expression needed to |
|
* enable the pin's signal for the requested function. |
|
* |
|
* @exprs: List of signal expressions (haystack) |
|
* @name: The name of the requested function (needle) |
|
* |
|
* Return: A pointer to the signal expression whose function tag matches the |
|
* provided name, otherwise NULL. |
|
* |
|
*/ |
|
static const struct aspeed_sig_expr *aspeed_find_expr_by_name( |
|
const struct aspeed_sig_expr **exprs, const char *name) |
|
{ |
|
while (*exprs) { |
|
if (strcmp((*exprs)->function, name) == 0) |
|
return *exprs; |
|
exprs++; |
|
} |
|
|
|
return NULL; |
|
} |
|
|
|
static char *get_defined_attribute(const struct aspeed_pin_desc *pdesc, |
|
const char *(*get)( |
|
const struct aspeed_sig_expr *)) |
|
{ |
|
char *found = NULL; |
|
size_t len = 0; |
|
const struct aspeed_sig_expr ***prios, **funcs, *expr; |
|
|
|
prios = pdesc->prios; |
|
|
|
while ((funcs = *prios)) { |
|
while ((expr = *funcs)) { |
|
const char *str = get(expr); |
|
size_t delta = strlen(str) + 2; |
|
char *expanded; |
|
|
|
expanded = krealloc(found, len + delta + 1, GFP_KERNEL); |
|
if (!expanded) { |
|
kfree(found); |
|
return expanded; |
|
} |
|
|
|
found = expanded; |
|
found[len] = '\0'; |
|
len += delta; |
|
|
|
strcat(found, str); |
|
strcat(found, ", "); |
|
|
|
funcs++; |
|
} |
|
prios++; |
|
} |
|
|
|
if (len < 2) { |
|
kfree(found); |
|
return NULL; |
|
} |
|
|
|
found[len - 2] = '\0'; |
|
|
|
return found; |
|
} |
|
|
|
static const char *aspeed_sig_expr_function(const struct aspeed_sig_expr *expr) |
|
{ |
|
return expr->function; |
|
} |
|
|
|
static char *get_defined_functions(const struct aspeed_pin_desc *pdesc) |
|
{ |
|
return get_defined_attribute(pdesc, aspeed_sig_expr_function); |
|
} |
|
|
|
static const char *aspeed_sig_expr_signal(const struct aspeed_sig_expr *expr) |
|
{ |
|
return expr->signal; |
|
} |
|
|
|
static char *get_defined_signals(const struct aspeed_pin_desc *pdesc) |
|
{ |
|
return get_defined_attribute(pdesc, aspeed_sig_expr_signal); |
|
} |
|
|
|
int aspeed_pinmux_set_mux(struct pinctrl_dev *pctldev, unsigned int function, |
|
unsigned int group) |
|
{ |
|
int i; |
|
int ret; |
|
struct aspeed_pinctrl_data *pdata = pinctrl_dev_get_drvdata(pctldev); |
|
const struct aspeed_pin_group *pgroup = &pdata->pinmux.groups[group]; |
|
const struct aspeed_pin_function *pfunc = |
|
&pdata->pinmux.functions[function]; |
|
|
|
for (i = 0; i < pgroup->npins; i++) { |
|
int pin = pgroup->pins[i]; |
|
const struct aspeed_pin_desc *pdesc = pdata->pins[pin].drv_data; |
|
const struct aspeed_sig_expr *expr = NULL; |
|
const struct aspeed_sig_expr **funcs; |
|
const struct aspeed_sig_expr ***prios; |
|
|
|
pr_debug("Muxing pin %s for %s\n", pdesc->name, pfunc->name); |
|
|
|
if (!pdesc) |
|
return -EINVAL; |
|
|
|
prios = pdesc->prios; |
|
|
|
if (!prios) |
|
continue; |
|
|
|
/* Disable functions at a higher priority than that requested */ |
|
while ((funcs = *prios)) { |
|
expr = aspeed_find_expr_by_name(funcs, pfunc->name); |
|
|
|
if (expr) |
|
break; |
|
|
|
ret = aspeed_disable_sig(&pdata->pinmux, funcs); |
|
if (ret) |
|
return ret; |
|
|
|
prios++; |
|
} |
|
|
|
if (!expr) { |
|
char *functions = get_defined_functions(pdesc); |
|
char *signals = get_defined_signals(pdesc); |
|
|
|
pr_warn("No function %s found on pin %s (%d). Found signal(s) %s for function(s) %s\n", |
|
pfunc->name, pdesc->name, pin, signals, |
|
functions); |
|
kfree(signals); |
|
kfree(functions); |
|
|
|
return -ENXIO; |
|
} |
|
|
|
ret = aspeed_sig_expr_enable(&pdata->pinmux, expr); |
|
if (ret) |
|
return ret; |
|
|
|
pr_debug("Muxed pin %s as %s for %s\n", pdesc->name, expr->signal, |
|
expr->function); |
|
} |
|
|
|
return 0; |
|
} |
|
|
|
static bool aspeed_expr_is_gpio(const struct aspeed_sig_expr *expr) |
|
{ |
|
/* |
|
* We need to differentiate between GPIO and non-GPIO signals to |
|
* implement the gpio_request_enable() interface. For better or worse |
|
* the ASPEED pinctrl driver uses the expression names to determine |
|
* whether an expression will mux a pin for GPIO. |
|
* |
|
* Generally we have the following - A GPIO such as B1 has: |
|
* |
|
* - expr->signal set to "GPIOB1" |
|
* - expr->function set to "GPIOB1" |
|
* |
|
* Using this fact we can determine whether the provided expression is |
|
* a GPIO expression by testing the signal name for the string prefix |
|
* "GPIO". |
|
* |
|
* However, some GPIOs are input-only, and the ASPEED datasheets name |
|
* them differently. An input-only GPIO such as T0 has: |
|
* |
|
* - expr->signal set to "GPIT0" |
|
* - expr->function set to "GPIT0" |
|
* |
|
* It's tempting to generalise the prefix test from "GPIO" to "GPI" to |
|
* account for both GPIOs and GPIs, but in doing so we run aground on |
|
* another feature: |
|
* |
|
* Some pins in the ASPEED BMC SoCs have a "pass-through" GPIO |
|
* function where the input state of one pin is replicated as the |
|
* output state of another (as if they were shorted together - a mux |
|
* configuration that is typically enabled by hardware strapping). |
|
* This feature allows the BMC to pass e.g. power button state through |
|
* to the host while the BMC is yet to boot, but take control of the |
|
* button state once the BMC has booted by muxing each pin as a |
|
* separate, pin-specific GPIO. |
|
* |
|
* Conceptually this pass-through mode is a form of GPIO and is named |
|
* as such in the datasheets, e.g. "GPID0". This naming similarity |
|
* trips us up with the simple GPI-prefixed-signal-name scheme |
|
* discussed above, as the pass-through configuration is not what we |
|
* want when muxing a pin as GPIO for the GPIO subsystem. |
|
* |
|
* On e.g. the AST2400, a pass-through function "GPID0" is grouped on |
|
* balls A18 and D16, where we have: |
|
* |
|
* For ball A18: |
|
* - expr->signal set to "GPID0IN" |
|
* - expr->function set to "GPID0" |
|
* |
|
* For ball D16: |
|
* - expr->signal set to "GPID0OUT" |
|
* - expr->function set to "GPID0" |
|
* |
|
* By contrast, the pin-specific GPIO expressions for the same pins are |
|
* as follows: |
|
* |
|
* For ball A18: |
|
* - expr->signal looks like "GPIOD0" |
|
* - expr->function looks like "GPIOD0" |
|
* |
|
* For ball D16: |
|
* - expr->signal looks like "GPIOD1" |
|
* - expr->function looks like "GPIOD1" |
|
* |
|
* Testing both the signal _and_ function names gives us the means |
|
* differentiate the pass-through GPIO pinmux configuration from the |
|
* pin-specific configuration that the GPIO subsystem is after: An |
|
* expression is a pin-specific (non-pass-through) GPIO configuration |
|
* if the signal prefix is "GPI" and the signal name matches the |
|
* function name. |
|
*/ |
|
return !strncmp(expr->signal, "GPI", 3) && |
|
!strcmp(expr->signal, expr->function); |
|
} |
|
|
|
static bool aspeed_gpio_in_exprs(const struct aspeed_sig_expr **exprs) |
|
{ |
|
if (!exprs) |
|
return false; |
|
|
|
while (*exprs) { |
|
if (aspeed_expr_is_gpio(*exprs)) |
|
return true; |
|
exprs++; |
|
} |
|
|
|
return false; |
|
} |
|
|
|
int aspeed_gpio_request_enable(struct pinctrl_dev *pctldev, |
|
struct pinctrl_gpio_range *range, |
|
unsigned int offset) |
|
{ |
|
int ret; |
|
struct aspeed_pinctrl_data *pdata = pinctrl_dev_get_drvdata(pctldev); |
|
const struct aspeed_pin_desc *pdesc = pdata->pins[offset].drv_data; |
|
const struct aspeed_sig_expr ***prios, **funcs, *expr; |
|
|
|
if (!pdesc) |
|
return -EINVAL; |
|
|
|
prios = pdesc->prios; |
|
|
|
if (!prios) |
|
return -ENXIO; |
|
|
|
pr_debug("Muxing pin %s for GPIO\n", pdesc->name); |
|
|
|
/* Disable any functions of higher priority than GPIO */ |
|
while ((funcs = *prios)) { |
|
if (aspeed_gpio_in_exprs(funcs)) |
|
break; |
|
|
|
ret = aspeed_disable_sig(&pdata->pinmux, funcs); |
|
if (ret) |
|
return ret; |
|
|
|
prios++; |
|
} |
|
|
|
if (!funcs) { |
|
char *signals = get_defined_signals(pdesc); |
|
|
|
pr_warn("No GPIO signal type found on pin %s (%d). Found: %s\n", |
|
pdesc->name, offset, signals); |
|
kfree(signals); |
|
|
|
return -ENXIO; |
|
} |
|
|
|
expr = *funcs; |
|
|
|
/* |
|
* Disabling all higher-priority expressions is enough to enable the |
|
* lowest-priority signal type. As such it has no associated |
|
* expression. |
|
*/ |
|
if (!expr) { |
|
pr_debug("Muxed pin %s as GPIO\n", pdesc->name); |
|
return 0; |
|
} |
|
|
|
/* |
|
* If GPIO is not the lowest priority signal type, assume there is only |
|
* one expression defined to enable the GPIO function |
|
*/ |
|
ret = aspeed_sig_expr_enable(&pdata->pinmux, expr); |
|
if (ret) |
|
return ret; |
|
|
|
pr_debug("Muxed pin %s as %s\n", pdesc->name, expr->signal); |
|
|
|
return 0; |
|
} |
|
|
|
int aspeed_pinctrl_probe(struct platform_device *pdev, |
|
struct pinctrl_desc *pdesc, |
|
struct aspeed_pinctrl_data *pdata) |
|
{ |
|
struct device *parent; |
|
struct pinctrl_dev *pctl; |
|
|
|
parent = pdev->dev.parent; |
|
if (!parent) { |
|
dev_err(&pdev->dev, "No parent for syscon pincontroller\n"); |
|
return -ENODEV; |
|
} |
|
|
|
pdata->scu = syscon_node_to_regmap(parent->of_node); |
|
if (IS_ERR(pdata->scu)) { |
|
dev_err(&pdev->dev, "No regmap for syscon pincontroller parent\n"); |
|
return PTR_ERR(pdata->scu); |
|
} |
|
|
|
pdata->pinmux.maps[ASPEED_IP_SCU] = pdata->scu; |
|
|
|
pctl = pinctrl_register(pdesc, &pdev->dev, pdata); |
|
|
|
if (IS_ERR(pctl)) { |
|
dev_err(&pdev->dev, "Failed to register pinctrl\n"); |
|
return PTR_ERR(pctl); |
|
} |
|
|
|
platform_set_drvdata(pdev, pdata); |
|
|
|
return 0; |
|
} |
|
|
|
static inline bool pin_in_config_range(unsigned int offset, |
|
const struct aspeed_pin_config *config) |
|
{ |
|
return offset >= config->pins[0] && offset <= config->pins[1]; |
|
} |
|
|
|
static inline const struct aspeed_pin_config *find_pinconf_config( |
|
const struct aspeed_pinctrl_data *pdata, |
|
unsigned int offset, |
|
enum pin_config_param param) |
|
{ |
|
unsigned int i; |
|
|
|
for (i = 0; i < pdata->nconfigs; i++) { |
|
if (param == pdata->configs[i].param && |
|
pin_in_config_range(offset, &pdata->configs[i])) |
|
return &pdata->configs[i]; |
|
} |
|
|
|
return NULL; |
|
} |
|
|
|
enum aspeed_pin_config_map_type { MAP_TYPE_ARG, MAP_TYPE_VAL }; |
|
|
|
static const struct aspeed_pin_config_map *find_pinconf_map( |
|
const struct aspeed_pinctrl_data *pdata, |
|
enum pin_config_param param, |
|
enum aspeed_pin_config_map_type type, |
|
s64 value) |
|
{ |
|
int i; |
|
|
|
for (i = 0; i < pdata->nconfmaps; i++) { |
|
const struct aspeed_pin_config_map *elem; |
|
bool match; |
|
|
|
elem = &pdata->confmaps[i]; |
|
|
|
switch (type) { |
|
case MAP_TYPE_ARG: |
|
match = (elem->arg == -1 || elem->arg == value); |
|
break; |
|
case MAP_TYPE_VAL: |
|
match = (elem->val == value); |
|
break; |
|
} |
|
|
|
if (param == elem->param && match) |
|
return elem; |
|
} |
|
|
|
return NULL; |
|
} |
|
|
|
int aspeed_pin_config_get(struct pinctrl_dev *pctldev, unsigned int offset, |
|
unsigned long *config) |
|
{ |
|
const enum pin_config_param param = pinconf_to_config_param(*config); |
|
const struct aspeed_pin_config_map *pmap; |
|
const struct aspeed_pinctrl_data *pdata; |
|
const struct aspeed_pin_config *pconf; |
|
unsigned int val; |
|
int rc = 0; |
|
u32 arg; |
|
|
|
pdata = pinctrl_dev_get_drvdata(pctldev); |
|
pconf = find_pinconf_config(pdata, offset, param); |
|
if (!pconf) |
|
return -ENOTSUPP; |
|
|
|
rc = regmap_read(pdata->scu, pconf->reg, &val); |
|
if (rc < 0) |
|
return rc; |
|
|
|
pmap = find_pinconf_map(pdata, param, MAP_TYPE_VAL, |
|
(val & pconf->mask) >> __ffs(pconf->mask)); |
|
|
|
if (!pmap) |
|
return -EINVAL; |
|
|
|
if (param == PIN_CONFIG_DRIVE_STRENGTH) |
|
arg = (u32) pmap->arg; |
|
else if (param == PIN_CONFIG_BIAS_PULL_DOWN) |
|
arg = !!pmap->arg; |
|
else |
|
arg = 1; |
|
|
|
if (!arg) |
|
return -EINVAL; |
|
|
|
*config = pinconf_to_config_packed(param, arg); |
|
|
|
return 0; |
|
} |
|
|
|
int aspeed_pin_config_set(struct pinctrl_dev *pctldev, unsigned int offset, |
|
unsigned long *configs, unsigned int num_configs) |
|
{ |
|
const struct aspeed_pinctrl_data *pdata; |
|
unsigned int i; |
|
int rc = 0; |
|
|
|
pdata = pinctrl_dev_get_drvdata(pctldev); |
|
|
|
for (i = 0; i < num_configs; i++) { |
|
const struct aspeed_pin_config_map *pmap; |
|
const struct aspeed_pin_config *pconf; |
|
enum pin_config_param param; |
|
unsigned int val; |
|
u32 arg; |
|
|
|
param = pinconf_to_config_param(configs[i]); |
|
arg = pinconf_to_config_argument(configs[i]); |
|
|
|
pconf = find_pinconf_config(pdata, offset, param); |
|
if (!pconf) |
|
return -ENOTSUPP; |
|
|
|
pmap = find_pinconf_map(pdata, param, MAP_TYPE_ARG, arg); |
|
|
|
if (WARN_ON(!pmap)) |
|
return -EINVAL; |
|
|
|
val = pmap->val << __ffs(pconf->mask); |
|
|
|
rc = regmap_update_bits(pdata->scu, pconf->reg, |
|
pconf->mask, val); |
|
|
|
if (rc < 0) |
|
return rc; |
|
|
|
pr_debug("%s: Set SCU%02X[0x%08X]=0x%X for param %d(=%d) on pin %d\n", |
|
__func__, pconf->reg, pconf->mask, |
|
val, param, arg, offset); |
|
} |
|
|
|
return 0; |
|
} |
|
|
|
int aspeed_pin_config_group_get(struct pinctrl_dev *pctldev, |
|
unsigned int selector, |
|
unsigned long *config) |
|
{ |
|
const unsigned int *pins; |
|
unsigned int npins; |
|
int rc; |
|
|
|
rc = aspeed_pinctrl_get_group_pins(pctldev, selector, &pins, &npins); |
|
if (rc < 0) |
|
return rc; |
|
|
|
if (!npins) |
|
return -ENODEV; |
|
|
|
rc = aspeed_pin_config_get(pctldev, pins[0], config); |
|
|
|
return rc; |
|
} |
|
|
|
int aspeed_pin_config_group_set(struct pinctrl_dev *pctldev, |
|
unsigned int selector, |
|
unsigned long *configs, |
|
unsigned int num_configs) |
|
{ |
|
const unsigned int *pins; |
|
unsigned int npins; |
|
int rc; |
|
int i; |
|
|
|
pr_debug("%s: Fetching pins for group selector %d\n", |
|
__func__, selector); |
|
rc = aspeed_pinctrl_get_group_pins(pctldev, selector, &pins, &npins); |
|
if (rc < 0) |
|
return rc; |
|
|
|
for (i = 0; i < npins; i++) { |
|
rc = aspeed_pin_config_set(pctldev, pins[i], configs, |
|
num_configs); |
|
if (rc < 0) |
|
return rc; |
|
} |
|
|
|
return 0; |
|
}
|
|
|