blob: 6fa203a3b8616e5add7998e27616de0d4c5115c3 [file] [log] [blame]
// SPDX-License-Identifier: GPL-2.0
/*
* Xilinx pinctrl driver for ZynqMP
*
* Author(s): Ashok Reddy Soma <ashok.reddy.soma@xilinx.com>
* Michal Simek <michal.simek@amd.com>
*
* Copyright (C) 2021 Xilinx, Inc. All rights reserved.
*/
#include <dm.h>
#include <errno.h>
#include <malloc.h>
#include <zynqmp_firmware.h>
#include <asm/arch/sys_proto.h>
#include <asm/io.h>
#include <dm/device_compat.h>
#include <dm/pinctrl.h>
#include <linux/compat.h>
#include <dt-bindings/pinctrl/pinctrl-zynqmp.h>
#define PINCTRL_GET_FUNC_GROUPS_RESP_LEN 12
#define PINCTRL_GET_PIN_GROUPS_RESP_LEN 12
#define NUM_GROUPS_PER_RESP 6
#define NA_GROUP -1
#define RESERVED_GROUP -2
#define MAX_GROUP_PIN 50
#define MAX_PIN_GROUPS 50
#define MAX_GROUP_NAME_LEN 32
#define MAX_FUNC_NAME_LEN 16
#define DRIVE_STRENGTH_2MA 2
#define DRIVE_STRENGTH_4MA 4
#define DRIVE_STRENGTH_8MA 8
#define DRIVE_STRENGTH_12MA 12
/*
* This driver works with very simple configuration that has the same name
* for group and function. This way it is compatible with the Linux Kernel
* driver.
*/
struct zynqmp_pinctrl_priv {
u32 npins;
u32 nfuncs;
u32 ngroups;
struct zynqmp_pmux_function *funcs;
struct zynqmp_pctrl_group *groups;
};
/**
* struct zynqmp_pinctrl_config - pinconfig parameters
* @slew: Slew rate slow or fast
* @bias: Bias enabled or disabled
* @pull_ctrl: Pull control pull up or pull down
* @input_type: CMOS or Schmitt
* @drive_strength: Drive strength 2mA/4mA/8mA/12mA
* @volt_sts: Voltage status 1.8V or 3.3V
* @tri_state: Tristate enabled or disabled
*
* This structure holds information about pin control config
* option that can be set for each pin.
*/
struct zynqmp_pinctrl_config {
u32 slew;
u32 bias;
u32 pull_ctrl;
u32 input_type;
u32 drive_strength;
u32 volt_sts;
u32 tri_state;
};
/**
* enum zynqmp_pin_config_param - possible pin configuration parameters
* @PIN_CFG_IOSTANDARD: if the pin can select an IO standard,
* the argument to this parameter (on a
* custom format) tells the driver which
* alternative IO standard to use
* @PIN_CONFIG_SCHMITTCMOS: this parameter (on a custom format) allows
* to select schmitt or cmos input for MIO pins
*/
enum zynqmp_pin_config_param {
PIN_CFG_IOSTANDARD = PIN_CONFIG_END + 1,
PIN_CONFIG_SCHMITTCMOS,
};
/**
* struct zynqmp_pmux_function - a pinmux function
* @name: Name of the pinmux function
* @groups: List of pingroups for this function
* @ngroups: Number of entries in @groups
*
* This structure holds information about pin control function
* and function group names supporting that function.
*/
struct zynqmp_pmux_function {
char name[MAX_FUNC_NAME_LEN];
const char * const *groups;
unsigned int ngroups;
};
/**
* struct zynqmp_pctrl_group - Pin control group info
* @name: Group name
* @pins: Group pin numbers
* @npins: Number of pins in group
*/
struct zynqmp_pctrl_group {
const char *name;
unsigned int pins[MAX_GROUP_PIN];
unsigned int npins;
};
static char pin_name[PINNAME_SIZE];
/**
* zynqmp_pm_query_data() - Get query data from firmware
* @qid: Value of enum pm_query_id
* @arg1: Argument 1
* @arg2: Argument 2
* @out: Returned output value
*
* Return: Returns status, either success or error+reason
*/
static int zynqmp_pm_query_data(enum pm_query_id qid, u32 arg1, u32 arg2, u32 *out)
{
int ret;
u32 ret_payload[PAYLOAD_ARG_CNT];
ret = xilinx_pm_request(PM_QUERY_DATA, qid, arg1, arg2, 0, ret_payload);
if (ret)
return ret;
*out = ret_payload[1];
return ret;
}
static int zynqmp_pm_pinctrl_get_config(const u32 pin, const u32 param, u32 *value)
{
int ret;
u32 ret_payload[PAYLOAD_ARG_CNT];
/* Get config for the pin */
ret = xilinx_pm_request(PM_PINCTRL_CONFIG_PARAM_GET, pin, param, 0, 0, ret_payload);
if (ret) {
printf("%s failed\n", __func__);
return ret;
}
*value = ret_payload[1];
return ret;
}
static int zynqmp_pm_pinctrl_set_config(const u32 pin, const u32 param, u32 value)
{
int ret;
if (param == PM_PINCTRL_CONFIG_TRI_STATE) {
ret = zynqmp_pm_feature(PM_PINCTRL_CONFIG_PARAM_SET);
if (ret < PM_PINCTRL_PARAM_SET_VERSION)
return -EOPNOTSUPP;
}
/* Request the pin first */
ret = xilinx_pm_request(PM_PINCTRL_REQUEST, pin, 0, 0, 0, NULL);
if (ret) {
printf("%s: pin request failed\n", __func__);
return ret;
}
/* Set config for the pin */
ret = xilinx_pm_request(PM_PINCTRL_CONFIG_PARAM_SET, pin, param, value, 0, NULL);
if (ret) {
printf("%s failed\n", __func__);
return ret;
}
return ret;
}
static int zynqmp_pinctrl_get_function_groups(u32 fid, u32 index, u16 *groups)
{
int ret;
u32 ret_payload[PAYLOAD_ARG_CNT];
ret = xilinx_pm_request(PM_QUERY_DATA, PM_QID_PINCTRL_GET_FUNCTION_GROUPS,
fid, index, 0, ret_payload);
if (ret) {
printf("%s failed\n", __func__);
return ret;
}
memcpy(groups, &ret_payload[1], PINCTRL_GET_FUNC_GROUPS_RESP_LEN);
return ret;
}
static int zynqmp_pinctrl_prepare_func_groups(u32 fid,
struct zynqmp_pmux_function *func,
struct zynqmp_pctrl_group *groups)
{
const char **fgroups;
char name[MAX_GROUP_NAME_LEN];
u16 resp[NUM_GROUPS_PER_RESP] = {0};
int ret, index, i;
fgroups = kcalloc(func->ngroups, sizeof(*fgroups), GFP_KERNEL);
if (!fgroups)
return -ENOMEM;
for (index = 0; index < func->ngroups; index += NUM_GROUPS_PER_RESP) {
ret = zynqmp_pinctrl_get_function_groups(fid, index, resp);
if (ret)
return ret;
for (i = 0; i < NUM_GROUPS_PER_RESP; i++) {
if (resp[i] == (u16)NA_GROUP)
goto done;
if (resp[i] == (u16)RESERVED_GROUP)
continue;
snprintf(name, MAX_GROUP_NAME_LEN, "%s_%d_grp",
func->name, index + i);
fgroups[index + i] = strdup(name);
snprintf(name, MAX_GROUP_NAME_LEN, "%s_%d_grp",
func->name, index + i);
groups[resp[i]].name = strdup(name);
}
}
done:
func->groups = fgroups;
return ret;
}
static int zynqmp_pinctrl_get_pin_groups(u32 pin, u32 index, u16 *groups)
{
int ret;
u32 ret_payload[PAYLOAD_ARG_CNT];
ret = xilinx_pm_request(PM_QUERY_DATA, PM_QID_PINCTRL_GET_PIN_GROUPS,
pin, index, 0, ret_payload);
if (ret) {
printf("%s failed to get pin groups\n", __func__);
return ret;
}
memcpy(groups, &ret_payload[1], PINCTRL_GET_PIN_GROUPS_RESP_LEN);
return ret;
}
static void zynqmp_pinctrl_group_add_pin(struct zynqmp_pctrl_group *group,
unsigned int pin)
{
group->pins[group->npins++] = pin;
}
static int zynqmp_pinctrl_create_pin_groups(struct zynqmp_pctrl_group *groups,
unsigned int pin)
{
u16 resp[NUM_GROUPS_PER_RESP] = {0};
int ret, i, index = 0;
do {
ret = zynqmp_pinctrl_get_pin_groups(pin, index, resp);
if (ret)
return ret;
for (i = 0; i < NUM_GROUPS_PER_RESP; i++) {
if (resp[i] == (u16)NA_GROUP)
goto done;
if (resp[i] == (u16)RESERVED_GROUP)
continue;
zynqmp_pinctrl_group_add_pin(&groups[resp[i]], pin);
}
index += NUM_GROUPS_PER_RESP;
} while (index <= MAX_PIN_GROUPS);
done:
return ret;
}
static int zynqmp_pinctrl_probe(struct udevice *dev)
{
struct zynqmp_pinctrl_priv *priv = dev_get_priv(dev);
int ret, i;
u32 pin;
u32 ret_payload[PAYLOAD_ARG_CNT];
/* Get number of pins first */
ret = zynqmp_pm_query_data(PM_QID_PINCTRL_GET_NUM_PINS, 0, 0, &priv->npins);
if (ret) {
printf("%s failed to get no of pins\n", __func__);
return ret;
}
/* Get number of functions available */
ret = zynqmp_pm_query_data(PM_QID_PINCTRL_GET_NUM_FUNCTIONS, 0, 0, &priv->nfuncs);
if (ret) {
printf("%s failed to get no of functions\n", __func__);
return ret;
}
/* Allocating structures for functions and its groups */
priv->funcs = kzalloc(sizeof(*priv->funcs) * priv->nfuncs, GFP_KERNEL);
if (!priv->funcs)
return -ENOMEM;
for (i = 0; i < priv->nfuncs; i++) {
/* Get function name for the function and fill */
xilinx_pm_request(PM_QUERY_DATA, PM_QID_PINCTRL_GET_FUNCTION_NAME,
i, 0, 0, ret_payload);
memcpy((void *)priv->funcs[i].name, ret_payload, MAX_FUNC_NAME_LEN);
/* And fill number of groups available for certain function */
xilinx_pm_request(PM_QUERY_DATA, PM_QID_PINCTRL_GET_NUM_FUNCTION_GROUPS,
i, 0, 0, ret_payload);
priv->funcs[i].ngroups = ret_payload[1];
priv->ngroups += priv->funcs[i].ngroups;
}
/* Prepare all groups */
priv->groups = kzalloc(sizeof(*priv->groups) * priv->ngroups,
GFP_KERNEL);
if (!priv->groups)
return -ENOMEM;
for (i = 0; i < priv->nfuncs; i++) {
ret = zynqmp_pinctrl_prepare_func_groups(i, &priv->funcs[i],
priv->groups);
if (ret) {
printf("Failed to prepare_func_groups\n");
return ret;
}
}
for (pin = 0; pin < priv->npins; pin++) {
ret = zynqmp_pinctrl_create_pin_groups(priv->groups, pin);
if (ret)
return ret;
}
return 0;
}
static int zynqmp_pinctrl_get_functions_count(struct udevice *dev)
{
struct zynqmp_pinctrl_priv *priv = dev_get_priv(dev);
return priv->nfuncs;
}
static const char *zynqmp_pinctrl_get_function_name(struct udevice *dev,
unsigned int selector)
{
struct zynqmp_pinctrl_priv *priv = dev_get_priv(dev);
return priv->funcs[selector].name;
}
static int zynqmp_pinmux_set(struct udevice *dev, unsigned int selector,
unsigned int func_selector)
{
int ret;
/* Request the pin first */
ret = xilinx_pm_request(PM_PINCTRL_REQUEST, selector, 0, 0, 0, NULL);
if (ret) {
printf("%s: pin request failed\n", __func__);
return ret;
}
/* Set the pin function */
ret = xilinx_pm_request(PM_PINCTRL_SET_FUNCTION, selector, func_selector,
0, 0, NULL);
if (ret) {
printf("%s: Failed to set pinmux function\n", __func__);
return ret;
}
return 0;
}
static int zynqmp_pinmux_group_set(struct udevice *dev, unsigned int selector,
unsigned int func_selector)
{
int i;
struct zynqmp_pinctrl_priv *priv = dev_get_priv(dev);
const struct zynqmp_pctrl_group *pgrp = &priv->groups[selector];
for (i = 0; i < pgrp->npins; i++)
zynqmp_pinmux_set(dev, pgrp->pins[i], func_selector);
return 0;
}
static int zynqmp_pinconf_set(struct udevice *dev, unsigned int pin,
unsigned int param, unsigned int arg)
{
int ret = 0;
unsigned int value;
switch (param) {
case PIN_CONFIG_SLEW_RATE:
param = PM_PINCTRL_CONFIG_SLEW_RATE;
ret = zynqmp_pm_pinctrl_set_config(pin, param, arg);
break;
case PIN_CONFIG_BIAS_PULL_UP:
param = PM_PINCTRL_CONFIG_PULL_CTRL;
arg = PM_PINCTRL_BIAS_PULL_UP;
ret = zynqmp_pm_pinctrl_set_config(pin, param, arg);
break;
case PIN_CONFIG_BIAS_PULL_DOWN:
param = PM_PINCTRL_CONFIG_PULL_CTRL;
arg = PM_PINCTRL_BIAS_PULL_DOWN;
ret = zynqmp_pm_pinctrl_set_config(pin, param, arg);
break;
case PIN_CONFIG_BIAS_DISABLE:
param = PM_PINCTRL_CONFIG_BIAS_STATUS;
arg = PM_PINCTRL_BIAS_DISABLE;
ret = zynqmp_pm_pinctrl_set_config(pin, param, arg);
break;
case PIN_CONFIG_SCHMITTCMOS:
param = PM_PINCTRL_CONFIG_SCHMITT_CMOS;
ret = zynqmp_pm_pinctrl_set_config(pin, param, arg);
break;
case PIN_CONFIG_INPUT_SCHMITT_ENABLE:
param = PM_PINCTRL_CONFIG_SCHMITT_CMOS;
ret = zynqmp_pm_pinctrl_set_config(pin, param, arg);
break;
case PIN_CONFIG_DRIVE_STRENGTH:
switch (arg) {
case DRIVE_STRENGTH_2MA:
value = PM_PINCTRL_DRIVE_STRENGTH_2MA;
break;
case DRIVE_STRENGTH_4MA:
value = PM_PINCTRL_DRIVE_STRENGTH_4MA;
break;
case DRIVE_STRENGTH_8MA:
value = PM_PINCTRL_DRIVE_STRENGTH_8MA;
break;
case DRIVE_STRENGTH_12MA:
value = PM_PINCTRL_DRIVE_STRENGTH_12MA;
break;
default:
/* Invalid drive strength */
dev_warn(dev, "Invalid drive strength for pin %d\n", pin);
return -EINVAL;
}
param = PM_PINCTRL_CONFIG_DRIVE_STRENGTH;
ret = zynqmp_pm_pinctrl_set_config(pin, param, value);
break;
case PIN_CFG_IOSTANDARD:
param = PM_PINCTRL_CONFIG_VOLTAGE_STATUS;
ret = zynqmp_pm_pinctrl_get_config(pin, param, &value);
if (arg != value)
dev_warn(dev, "Invalid IO Standard requested for pin %d\n",
pin);
break;
case PIN_CONFIG_POWER_SOURCE:
param = PM_PINCTRL_CONFIG_VOLTAGE_STATUS;
ret = zynqmp_pm_pinctrl_get_config(pin, param, &value);
if (arg != value)
dev_warn(dev, "Invalid IO Standard requested for pin %d\n",
pin);
break;
case PIN_CONFIG_BIAS_HIGH_IMPEDANCE:
param = PM_PINCTRL_CONFIG_TRI_STATE;
arg = PM_PINCTRL_TRI_STATE_ENABLE;
ret = zynqmp_pm_pinctrl_set_config(pin, param, arg);
break;
case PIN_CONFIG_LOW_POWER_MODE:
/*
* This cases are mentioned in dts but configurable
* registers are unknown. So falling through to ignore
* boot time warnings as of now.
*/
ret = 0;
break;
case PIN_CONFIG_OUTPUT_ENABLE:
param = PM_PINCTRL_CONFIG_TRI_STATE;
arg = PM_PINCTRL_TRI_STATE_DISABLE;
ret = zynqmp_pm_pinctrl_set_config(pin, param, arg);
break;
default:
dev_warn(dev, "unsupported configuration parameter '%u'\n",
param);
ret = -ENOTSUPP;
break;
}
return ret;
}
static int zynqmp_pinconf_group_set(struct udevice *dev,
unsigned int group_selector,
unsigned int param, unsigned int arg)
{
int i;
struct zynqmp_pinctrl_priv *priv = dev_get_priv(dev);
const struct zynqmp_pctrl_group *pgrp = &priv->groups[group_selector];
for (i = 0; i < pgrp->npins; i++)
zynqmp_pinconf_set(dev, pgrp->pins[i], param, arg);
return 0;
}
static int zynqmp_pinctrl_get_pins_count(struct udevice *dev)
{
struct zynqmp_pinctrl_priv *priv = dev_get_priv(dev);
return priv->npins;
}
static const char *zynqmp_pinctrl_get_pin_name(struct udevice *dev,
unsigned int selector)
{
snprintf(pin_name, PINNAME_SIZE, "MIO%d", selector);
return pin_name;
}
static int zynqmp_pinctrl_get_pin_muxing(struct udevice *dev,
unsigned int selector,
char *buf,
int size)
{
struct zynqmp_pinctrl_config pinmux;
zynqmp_pm_pinctrl_get_config(selector, PM_PINCTRL_CONFIG_SLEW_RATE,
&pinmux.slew);
zynqmp_pm_pinctrl_get_config(selector, PM_PINCTRL_CONFIG_BIAS_STATUS,
&pinmux.bias);
zynqmp_pm_pinctrl_get_config(selector, PM_PINCTRL_CONFIG_PULL_CTRL,
&pinmux.pull_ctrl);
zynqmp_pm_pinctrl_get_config(selector, PM_PINCTRL_CONFIG_SCHMITT_CMOS,
&pinmux.input_type);
zynqmp_pm_pinctrl_get_config(selector, PM_PINCTRL_CONFIG_DRIVE_STRENGTH,
&pinmux.drive_strength);
zynqmp_pm_pinctrl_get_config(selector, PM_PINCTRL_CONFIG_VOLTAGE_STATUS,
&pinmux.volt_sts);
zynqmp_pm_pinctrl_get_config(selector, PM_PINCTRL_CONFIG_TRI_STATE,
&pinmux.tri_state);
switch (pinmux.drive_strength) {
case PM_PINCTRL_DRIVE_STRENGTH_2MA:
pinmux.drive_strength = DRIVE_STRENGTH_2MA;
break;
case PM_PINCTRL_DRIVE_STRENGTH_4MA:
pinmux.drive_strength = DRIVE_STRENGTH_4MA;
break;
case PM_PINCTRL_DRIVE_STRENGTH_8MA:
pinmux.drive_strength = DRIVE_STRENGTH_8MA;
break;
case PM_PINCTRL_DRIVE_STRENGTH_12MA:
pinmux.drive_strength = DRIVE_STRENGTH_12MA;
break;
default:
/* Invalid drive strength */
dev_warn(dev, "Invalid drive strength\n");
return -EINVAL;
}
snprintf(buf, size,
"slew:%s\tbias:%s\tpull:%s\tinput:%s\tdrive:%dmA\tvolt:%s\ttri_state:%s",
pinmux.slew ? "slow" : "fast",
pinmux.bias ? "enabled" : "disabled",
pinmux.pull_ctrl ? "up" : "down",
pinmux.input_type ? "schmitt" : "cmos",
pinmux.drive_strength,
pinmux.volt_sts ? "1.8" : "3.3",
pinmux.tri_state ? "enabled" : "disabled");
return 0;
}
static int zynqmp_pinctrl_get_groups_count(struct udevice *dev)
{
struct zynqmp_pinctrl_priv *priv = dev_get_priv(dev);
return priv->ngroups;
}
static const char *zynqmp_pinctrl_get_group_name(struct udevice *dev,
unsigned int selector)
{
struct zynqmp_pinctrl_priv *priv = dev_get_priv(dev);
return priv->groups[selector].name;
}
static const struct pinconf_param zynqmp_conf_params[] = {
{ "bias-bus-hold", PIN_CONFIG_BIAS_BUS_HOLD, 0 },
{ "bias-disable", PIN_CONFIG_BIAS_DISABLE, 0 },
{ "bias-high-impedance", PIN_CONFIG_BIAS_HIGH_IMPEDANCE, 0 },
{ "bias-pull-up", PIN_CONFIG_BIAS_PULL_UP, 1 },
{ "bias-pull-pin-default", PIN_CONFIG_BIAS_PULL_PIN_DEFAULT, 1 },
{ "bias-pull-down", PIN_CONFIG_BIAS_PULL_DOWN, 1 },
{ "drive-open-drain", PIN_CONFIG_DRIVE_OPEN_DRAIN, 0 },
{ "drive-open-source", PIN_CONFIG_DRIVE_OPEN_SOURCE, 0 },
{ "drive-push-pull", PIN_CONFIG_DRIVE_PUSH_PULL, 0 },
{ "drive-strength", PIN_CONFIG_DRIVE_STRENGTH, 0 },
{ "drive-strength-microamp", PIN_CONFIG_DRIVE_STRENGTH_UA, 0 },
{ "input-debounce", PIN_CONFIG_INPUT_DEBOUNCE, 0 },
{ "input-disable", PIN_CONFIG_INPUT_ENABLE, 0 },
{ "input-enable", PIN_CONFIG_INPUT_ENABLE, 1 },
{ "input-schmitt", PIN_CONFIG_INPUT_SCHMITT, 0 },
{ "input-schmitt-disable", PIN_CONFIG_INPUT_SCHMITT_ENABLE, 0 },
{ "input-schmitt-enable", PIN_CONFIG_INPUT_SCHMITT_ENABLE, 1 },
{ "low-power-disable", PIN_CONFIG_LOW_POWER_MODE, 0 },
{ "low-power-enable", PIN_CONFIG_LOW_POWER_MODE, 1 },
{ "output-disable", PIN_CONFIG_OUTPUT_ENABLE, 0 },
{ "output-enable", PIN_CONFIG_OUTPUT_ENABLE, 1 },
{ "output-high", PIN_CONFIG_OUTPUT, 1, },
{ "output-low", PIN_CONFIG_OUTPUT, 0, },
{ "power-source", PIN_CONFIG_POWER_SOURCE, 0 },
{ "sleep-hardware-state", PIN_CONFIG_SLEEP_HARDWARE_STATE, 0 },
{ "slew-rate", PIN_CONFIG_SLEW_RATE, 0 },
{ "skew-delay", PIN_CONFIG_SKEW_DELAY, 0 },
/* zynqmp specific */
{"io-standard", PIN_CFG_IOSTANDARD, IO_STANDARD_LVCMOS18},
{"schmitt-cmos", PIN_CONFIG_SCHMITTCMOS, PM_PINCTRL_INPUT_TYPE_SCHMITT},
};
static struct pinctrl_ops zynqmp_pinctrl_ops = {
.get_pins_count = zynqmp_pinctrl_get_pins_count,
.get_pin_name = zynqmp_pinctrl_get_pin_name,
.get_pin_muxing = zynqmp_pinctrl_get_pin_muxing,
.set_state = pinctrl_generic_set_state,
.get_groups_count = zynqmp_pinctrl_get_groups_count,
.get_group_name = zynqmp_pinctrl_get_group_name,
.get_functions_count = zynqmp_pinctrl_get_functions_count,
.get_function_name = zynqmp_pinctrl_get_function_name,
.pinmux_group_set = zynqmp_pinmux_group_set,
.pinmux_set = zynqmp_pinmux_set,
.pinconf_params = zynqmp_conf_params,
.pinconf_group_set = zynqmp_pinconf_group_set,
.pinconf_set = zynqmp_pinconf_set,
.pinconf_num_params = ARRAY_SIZE(zynqmp_conf_params),
};
static const struct udevice_id zynqmp_pinctrl_ids[] = {
{ .compatible = "xlnx,zynqmp-pinctrl" },
{ }
};
U_BOOT_DRIVER(pinctrl_zynqmp) = {
.name = "zynqmp-pinctrl",
.id = UCLASS_PINCTRL,
.of_match = zynqmp_pinctrl_ids,
.priv_auto = sizeof(struct zynqmp_pinctrl_priv),
.ops = &zynqmp_pinctrl_ops,
.probe = zynqmp_pinctrl_probe,
};