/*
* Marvell MVEBU pinctrl core driver
*/
#include <linux/platform_device.h>
#include <linux/module.h>
#include <linux/slab.h>
#include <linux/io.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/of_platform.h>
#include <linux/err.h>
#include <linux/gpio.h>
#include <linux/pinctrl/machine.h>
#include <linux/pinctrl/pinconf.h>
#include <linux/pinctrl/pinctrl.h>
#include <linux/pinctrl/pinmux.h>
#include "pinctrl-mvebu.h"
#define MPPS_PER_REG 8
#define MPP_BITS 4
#define MPP_MASK 0xf
struct mvebu_pinctrl_function {
const char *name;
const char **groups;
unsigned num_groups;
};
struct mvebu_pinctrl_group {
const char *name;
struct mvebu_mpp_ctrl *ctrl;
struct mvebu_mpp_ctrl_setting *settings;
unsigned num_settings;
unsigned gid;
unsigned *pins;
unsigned npins;
};
struct mvebu_pinctrl {
struct device *dev;
struct pinctrl_dev *pctldev;
struct pinctrl_desc desc;
void __iomem *base;
struct mvebu_pinctrl_group *groups;
unsigned num_groups;
struct mvebu_pinctrl_function *functions;
unsigned num_functions;
u8 variant;
};
static struct mvebu_pinctrl_group *mvebu_pinctrl_find_group_by_pid(
struct mvebu_pinctrl *pctl, unsigned pid)
{
unsigned n;
for (n = 0; n < pctl->num_groups; n++) {
if (pid >= pctl->groups[n].pins[0] &&
pid < pctl->groups[n].pins[0] +
pctl->groups[n].npins)
return &pctl->groups[n];
}
return NULL;
}
static struct mvebu_pinctrl_group *mvebu_pinctrl_find_group_by_name(
struct mvebu_pinctrl *pctl, const char *name)
{
unsigned n;
for (n = 0; n < pctl->num_groups; n++) {
if (strcmp(name, pctl->groups[n].name) == 0)
return &pctl->groups[n];
}
return NULL;
}
static struct mvebu_mpp_ctrl_setting *mvebu_pinctrl_find_setting_by_val(
struct mvebu_pinctrl *pctl, struct mvebu_pinctrl_group *grp,
unsigned long config)
{
unsigned n;
for (n = 0; n < grp->num_settings; n++) {
if (config == grp->settings[n].val) {
if (!pctl->variant || (pctl->variant &
grp->settings[n].variant))
return &grp->settings[n];
}
}
return NULL;
}
static struct mvebu_mpp_ctrl_setting *mvebu_pinctrl_find_setting_by_name(
struct mvebu_pinctrl *pctl, struct mvebu_pinctrl_group *grp,
const char *name)
{
unsigned n;
for (n = 0; n < grp->num_settings; n++) {
if (strcmp(name, grp->settings[n].name) == 0) {
if (!pctl->variant || (pctl->variant &
grp->settings[n].variant))
return &grp->settings[n];
}
}
return NULL;
}
static struct mvebu_mpp_ctrl_setting *mvebu_pinctrl_find_gpio_setting(
struct mvebu_pinctrl *pctl, struct mvebu_pinctrl_group *grp)
{
unsigned n;
for (n = 0; n < grp->num_settings; n++) {
if (grp->settings[n].flags &
(MVEBU_SETTING_GPO | MVEBU_SETTING_GPI)) {
if (!pctl->variant || (pctl->variant &
grp->settings[n].variant))
return &grp->settings[n];
}
}
return NULL;
}
static struct mvebu_pinctrl_function *mvebu_pinctrl_find_function_by_name(
struct mvebu_pinctrl *pctl, const char *name)
{
unsigned n;
for (n = 0; n < pctl->num_functions; n++) {
if (strcmp(name, pctl->functions[n].name) == 0)
return &pctl->functions[n];
}
return NULL;
}
/*
* Common mpp pin configuration registers on MVEBU are
* registers of eight 4-bit values for each mpp setting.
* Register offset and bit mask are calculated accordingly below.
*/
static int mvebu_common_mpp_get(struct mvebu_pinctrl *pctl,
struct mvebu_pinctrl_group *grp,
unsigned long *config)
{
unsigned pin = grp->gid;
unsigned off = (pin / MPPS_PER_REG) * MPP_BITS;
unsigned shift = (pin % MPPS_PER_REG) * MPP_BITS;
*config = readl(pctl->base + off);
*config >>= shift;
*config &= MPP_MASK;
return 0;
}
static int mvebu_common_mpp_set(struct mvebu_pinctrl *pctl,
struct mvebu_pinctrl_group *grp,
unsigned long config)
{
unsigned pin = grp->gid;
unsigned off = (pin / MPPS_PER_REG) * MPP_BITS;
unsigned shift = (pin % MPPS_PER_REG) * MPP_BITS;
unsigned long reg;
reg = readl(pctl->base + off);
reg &= ~(MPP_MASK << shift);
reg |= (config << shift);
writel(reg, pctl->base + off);
return 0;
}
static int mvebu_pinconf_group_get(struct pinctrl_dev *pctldev,
unsigned gid, unsigned long *config)
{
struct mvebu_pinctrl *pctl = pinctrl_dev_get_drvdata(pctldev);
struct mvebu_pinctrl_group *grp = &pctl->groups[gid];
if (!grp->ctrl)
return -EINVAL;
if (grp->ctrl->mpp_get)
return grp->ctrl->mpp_get(grp->ctrl, config);
return mvebu_common_mpp_get(pctl, grp, config);
}
static int mvebu_pinconf_group_set(struct pinctrl_dev *pctldev,
unsigned gid, unsigned long *configs,
unsigned num_configs)
{
struct mvebu_pinctrl *pctl = pinctrl_dev_get_drvdata(pctldev);
struct mvebu_pinctrl_group *grp = &pctl->groups[gid];
int i, ret;
if (!grp->ctrl)
return -EINVAL;
for (i = 0; i < num_configs; i++) {
if (grp->ctrl->mpp_set)
ret = grp->ctrl->mpp_set(grp->ctrl, configs[i]);
else
ret = mvebu_common_mpp_set(pctl, grp, configs[i]);
if (ret)
return ret;
} /* for each config */
return 0;
}
static void mvebu_pinconf_group_dbg_show(struct pinctrl_dev *pctldev,
struct seq_file *s, unsigned gid)
{
struct mvebu_pinctrl *pctl = pinctrl_dev_get_drvdata(pctldev);
struct mvebu_pinctrl_group *grp = &pctl->groups[gid];
struct mvebu_mpp_ctrl_setting *curr;
unsigned long config;
unsigned n;
if (mvebu_pinconf_group_get(pctldev, gid, &config))
return;
curr = mvebu_pinctrl_find_setting_by_val(pctl, grp, config);
if (curr) {
seq_printf(s, "current: %s", curr->name);
if (curr->subname)
seq_printf(s, "(%s)", curr->subname);
if (curr->flags & (MVEBU_SETTING_GPO | MVEBU_SETTING_GPI)) {
seq_printf(s, "(");
if (curr->flags & MVEBU_SETTING_GPI)
seq_printf(s, "i");
if (curr->flags & MVEBU_SETTING_GPO)
seq_printf(s, "o");
seq_printf(s, ")");
}
} else
seq_printf(s, "current: UNKNOWN");
if (grp->num_settings > 1) {
seq_printf(s, ", available = [");
for (n = 0; n < grp->num_settings; n++) {
if (curr == &grp->settings[n])
continue;
/* skip unsupported settings for this variant */
if (pctl->variant &&
!(pctl->variant & grp->settings[n].variant))
continue;
seq_printf(s, " %s", grp->settings[n].name);
if (grp->settings[n].subname)
seq_printf(s, "(%s)", grp->settings[n].subname);
if (grp->settings[n].flags &
(MVEBU_SETTING_GPO | MVEBU_SETTING_GPI)) {
seq_printf(s, "(");
if (grp->settings[n].flags & MVEBU_SETTING_GPI)
seq_printf(s, "i");
if (grp->settings[n].flags & MVEBU_SETTING_GPO)
seq_printf(s, "o");
seq_printf(s, ")");
}
}
seq_printf(s, " ]");
}
return;
}
static const struct pinconf_ops mvebu_pinconf_ops = {
.pin_config_group_get = mvebu_pinconf_group_get,
.pin_config_group_set = mvebu_pinconf_group_set,
.pin_config_group_dbg_show = mvebu_pinconf_group_dbg_show,
};
static int mvebu_pinmux_get_funcs_count(struct pinctrl_dev *pctldev)
{
struct mvebu_pinctrl *pctl = pinctrl_dev_get_drvdata(pctldev);
return pctl->num_functions;
}
static const char *mvebu_pinmux_get_func_name(struct pinctrl_dev *pctldev,
unsigned fid)
{
struct mvebu_pinctrl *pctl = pinctrl_dev_get_drvdata(pctldev);
return pctl->functions[fid].name;
}
static int mvebu_pinmux_get_groups(struct pinctrl_dev *pctldev, unsigned fid,
const char * const **groups,
unsigned * const num_groups)
{
struct mvebu_pinctrl *pctl = pinctrl_dev_get_drvdata(pctldev);
*groups = pctl->functions[fid].groups;
*num_groups = pctl->functions[fid].num_groups;
return 0;
}
static int mvebu_pinmux_enable(struct pinctrl_dev *pctldev, unsigned fid,
unsigned gid)
{
struct mvebu_pinctrl *pctl = pinctrl_dev_get_drvdata(pctldev);
struct mvebu_pinctrl_function *func = &pctl->functions[fid];
struct mvebu_pinctrl_group *grp = &pctl->groups[gid];
struct mvebu_mpp_ctrl_setting *setting;
int ret;
unsigned long config;
setting = mvebu_pinctrl_find_setting_by_name(pctl, grp,
func->name);
if (!setting) {
dev_err(pctl->dev,
"unable to find setti