blob: a5b583f10441e42cea34c275bc61fb36b949c0e4 [file] [log] [blame] [edit]
// SPDX-License-Identifier: GPL-2.0+
//
// Author: Jerry Zhu <Jerry.Zhu@cixtech.com>
// Author: Gary Yang <gary.yang@cixtech.com>
#include <linux/device.h>
#include <linux/err.h>
#include <linux/init.h>
#include <linux/io.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/of_device.h>
#include <linux/of_address.h>
#include <linux/pinctrl/pinconf.h>
#include <linux/pinctrl/pinconf-generic.h>
#include <linux/pinctrl/pinctrl.h>
#include <linux/pinctrl/pinmux.h>
#include <linux/platform_device.h>
#include <linux/seq_file.h>
#include <linux/slab.h>
#include "../core.h"
#include "../pinconf.h"
#include "../pinctrl-utils.h"
#include "../pinmux.h"
#include "pinctrl-sky1.h"
#define SKY1_PIN_SIZE (4)
#define SKY1_MUX_MASK GENMASK(8, 7)
#define SKY1_MUX_SHIFT (7)
#define SKY1_PULLCONF_MASK GENMASK(6, 5)
#define SKY1_PULLUP_BIT (6)
#define SKY1_PULLDN_BIT (5)
#define SKY1_DS_MASK GENMASK(3, 0)
#define CIX_PIN_NO_SHIFT (8)
#define CIX_PIN_FUN_MASK GENMASK(1, 0)
#define CIX_GET_PIN_NO(x) ((x) >> CIX_PIN_NO_SHIFT)
#define CIX_GET_PIN_FUNC(x) ((x) & CIX_PIN_FUN_MASK)
#define SKY1_DEFAULT_DS_VAL (4)
static const char * const sky1_gpio_functions[] = {
"func0", "func1", "func2", "func3",
};
static unsigned char sky1_ds_table[] = {
2, 3, 5, 6, 8, 9, 11, 12, 13, 14, 17, 18, 20, 21, 23, 24,
};
static bool sky1_pctrl_is_function_valid(struct sky1_pinctrl *spctl,
u32 pin_num, u32 fnum)
{
int i;
for (i = 0; i < spctl->info->npins; i++) {
const struct sky1_pin_desc *pin = spctl->info->pins + i;
if (pin->pin.number == pin_num) {
if (fnum < pin->nfunc)
return true;
break;
}
}
return false;
}
static int sky1_pctrl_dt_node_to_map_func(struct sky1_pinctrl *spctl,
u32 pin, u32 fnum, struct sky1_pinctrl_group *grp,
struct pinctrl_map **map, unsigned int *reserved_maps,
unsigned int *num_maps)
{
bool ret;
if (*num_maps == *reserved_maps)
return -ENOSPC;
(*map)[*num_maps].type = PIN_MAP_TYPE_MUX_GROUP;
(*map)[*num_maps].data.mux.group = grp->name;
ret = sky1_pctrl_is_function_valid(spctl, pin, fnum);
if (!ret) {
dev_err(spctl->dev, "invalid function %d on pin %d .\n",
fnum, pin);
return -EINVAL;
}
(*map)[*num_maps].data.mux.function = sky1_gpio_functions[fnum];
(*num_maps)++;
return 0;
}
static struct sky1_pinctrl_group *
sky1_pctrl_find_group_by_pin(struct sky1_pinctrl *spctl, u32 pin)
{
int i;
for (i = 0; i < spctl->info->npins; i++) {
struct sky1_pinctrl_group *grp =
(struct sky1_pinctrl_group *)spctl->groups + i;
if (grp->pin == pin)
return grp;
}
return NULL;
}
static int sky1_pctrl_dt_subnode_to_map(struct pinctrl_dev *pctldev,
struct device_node *node,
struct pinctrl_map **map,
unsigned int *reserved_maps,
unsigned int *num_maps)
{
struct property *pins;
u32 pinfunc, pin, func;
int num_pins, num_funcs, maps_per_pin;
unsigned long *configs;
unsigned int num_configs;
bool has_config = false;
int i, err;
unsigned int reserve = 0;
struct sky1_pinctrl_group *grp;
struct sky1_pinctrl *spctl = pinctrl_dev_get_drvdata(pctldev);
pins = of_find_property(node, "pinmux", NULL);
if (!pins) {
dev_err(spctl->dev, "missing pins property in node %pOFn .\n",
node);
return -EINVAL;
}
err = pinconf_generic_parse_dt_config(node, pctldev, &configs,
&num_configs);
if (err)
return err;
if (num_configs)
has_config = true;
num_pins = pins->length / sizeof(u32);
num_funcs = num_pins;
maps_per_pin = 0;
if (num_funcs)
maps_per_pin++;
if (has_config && num_pins >= 1)
maps_per_pin++;
if (!num_pins || !maps_per_pin) {
err = -EINVAL;
goto exit;
}
reserve = num_pins * maps_per_pin;
err = pinctrl_utils_reserve_map(pctldev, map,
reserved_maps, num_maps, reserve);
if (err < 0)
goto exit;
for (i = 0; i < num_pins; i++) {
err = of_property_read_u32_index(node, "pinmux",
i, &pinfunc);
if (err)
goto exit;
pin = CIX_GET_PIN_NO(pinfunc);
func = CIX_GET_PIN_FUNC(pinfunc);
pctldev->num_functions = ARRAY_SIZE(sky1_gpio_functions);
if (pin >= pctldev->desc->npins ||
func >= pctldev->num_functions) {
dev_err(spctl->dev, "invalid pins value.\n");
err = -EINVAL;
goto exit;
}
grp = sky1_pctrl_find_group_by_pin(spctl, pin);
if (!grp) {
dev_err(spctl->dev, "unable to match pin %d to group\n",
pin);
err = -EINVAL;
goto exit;
}
err = sky1_pctrl_dt_node_to_map_func(spctl, pin, func, grp,
map, reserved_maps, num_maps);
if (err < 0)
goto exit;
if (has_config) {
err = pinctrl_utils_add_map_configs(pctldev, map,
reserved_maps, num_maps, grp->name,
configs, num_configs,
PIN_MAP_TYPE_CONFIGS_GROUP);
if (err < 0)
goto exit;
}
}
err = 0;
exit:
kfree(configs);
return err;
}
static int sky1_pctrl_dt_node_to_map(struct pinctrl_dev *pctldev,
struct device_node *np_config,
struct pinctrl_map **map, unsigned int *num_maps)
{
unsigned int reserved_maps;
int ret;
*map = NULL;
*num_maps = 0;
reserved_maps = 0;
for_each_child_of_node_scoped(np_config, np) {
ret = sky1_pctrl_dt_subnode_to_map(pctldev, np, map,
&reserved_maps, num_maps);
if (ret < 0) {
pinctrl_utils_free_map(pctldev, *map, *num_maps);
return ret;
}
}
return 0;
}
static void sky1_dt_free_map(struct pinctrl_dev *pctldev,
struct pinctrl_map *map,
unsigned int num_maps)
{
kfree(map);
}
static int sky1_pctrl_get_groups_count(struct pinctrl_dev *pctldev)
{
struct sky1_pinctrl *spctl = pinctrl_dev_get_drvdata(pctldev);
return spctl->info->npins;
}
static const char *sky1_pctrl_get_group_name(struct pinctrl_dev *pctldev,
unsigned int group)
{
struct sky1_pinctrl *spctl = pinctrl_dev_get_drvdata(pctldev);
return spctl->groups[group].name;
}
static int sky1_pctrl_get_group_pins(struct pinctrl_dev *pctldev,
unsigned int group,
const unsigned int **pins,
unsigned int *num_pins)
{
struct sky1_pinctrl *spctl = pinctrl_dev_get_drvdata(pctldev);
*pins = (unsigned int *)&spctl->groups[group].pin;
*num_pins = 1;
return 0;
}
static void sky1_pin_dbg_show(struct pinctrl_dev *pctldev, struct seq_file *s,
unsigned int offset)
{
seq_printf(s, "%s", dev_name(pctldev->dev));
}
static const struct pinctrl_ops sky1_pctrl_ops = {
.dt_node_to_map = sky1_pctrl_dt_node_to_map,
.dt_free_map = sky1_dt_free_map,
.get_groups_count = sky1_pctrl_get_groups_count,
.get_group_name = sky1_pctrl_get_group_name,
.get_group_pins = sky1_pctrl_get_group_pins,
.pin_dbg_show = sky1_pin_dbg_show,
};
static int sky1_pmx_set_one_pin(struct sky1_pinctrl *spctl,
unsigned int pin, unsigned char muxval)
{
u32 reg_val;
void __iomem *pin_reg;
pin_reg = spctl->base + pin * SKY1_PIN_SIZE;
reg_val = readl(pin_reg);
reg_val &= ~SKY1_MUX_MASK;
reg_val |= muxval << SKY1_MUX_SHIFT;
writel(reg_val, pin_reg);
dev_dbg(spctl->dev, "write: offset 0x%x val 0x%x\n",
pin * SKY1_PIN_SIZE, reg_val);
return 0;
}
static int sky1_pmx_set_mux(struct pinctrl_dev *pctldev,
unsigned int function,
unsigned int group)
{
bool ret;
struct sky1_pinctrl *spctl = pinctrl_dev_get_drvdata(pctldev);
struct sky1_pinctrl_group *g =
(struct sky1_pinctrl_group *)spctl->groups + group;
ret = sky1_pctrl_is_function_valid(spctl, g->pin, function);
if (!ret) {
dev_err(spctl->dev, "invalid function %d on group %d .\n",
function, group);
return -EINVAL;
}
sky1_pmx_set_one_pin(spctl, g->pin, function);
return 0;
}
static int sky1_pmx_get_funcs_cnt(struct pinctrl_dev *pctldev)
{
return ARRAY_SIZE(sky1_gpio_functions);
}
static const char *sky1_pmx_get_func_name(struct pinctrl_dev *pctldev,
unsigned int selector)
{
return sky1_gpio_functions[selector];
}
static int sky1_pmx_get_func_groups(struct pinctrl_dev *pctldev,
unsigned int function,
const char * const **groups,
unsigned int * const num_groups)
{
struct sky1_pinctrl *spctl = pinctrl_dev_get_drvdata(pctldev);
const struct sky1_pinctrl_soc_info *info = spctl->info;
*groups = spctl->grp_names;
*num_groups = info->npins;
return 0;
}
static const struct pinmux_ops sky1_pmx_ops = {
.get_functions_count = sky1_pmx_get_funcs_cnt,
.get_function_groups = sky1_pmx_get_func_groups,
.get_function_name = sky1_pmx_get_func_name,
.set_mux = sky1_pmx_set_mux,
};
static int sky1_pconf_set_pull_select(struct sky1_pinctrl *spctl,
unsigned int pin, bool enable, bool isup)
{
u32 reg_val, reg_pullsel = 0;
void __iomem *pin_reg;
pin_reg = spctl->base + pin * SKY1_PIN_SIZE;
reg_val = readl(pin_reg);
reg_val &= ~SKY1_PULLCONF_MASK;
if (!enable)
goto update;
if (isup)
reg_pullsel = BIT(SKY1_PULLUP_BIT);
else
reg_pullsel = BIT(SKY1_PULLDN_BIT);
update:
reg_val |= reg_pullsel;
writel(reg_val, pin_reg);
dev_dbg(spctl->dev, "write: offset 0x%x val 0x%x\n",
pin * SKY1_PIN_SIZE, reg_val);
return 0;
}
static int sky1_ds_to_index(unsigned char driving)
{
int i;
for (i = 0; i < sizeof(sky1_ds_table); i++)
if (driving == sky1_ds_table[i])
return i;
return SKY1_DEFAULT_DS_VAL;
}
static int sky1_pconf_set_driving(struct sky1_pinctrl *spctl,
unsigned int pin, unsigned char driving)
{
unsigned int reg_val, val;
void __iomem *pin_reg;
if (pin >= spctl->info->npins)
return -EINVAL;
pin_reg = spctl->base + pin * SKY1_PIN_SIZE;
reg_val = readl(pin_reg);
reg_val &= ~SKY1_DS_MASK;
val = sky1_ds_to_index(driving);
reg_val |= (val & SKY1_DS_MASK);
writel(reg_val, pin_reg);
dev_dbg(spctl->dev, "write: offset 0x%x val 0x%x\n",
pin * SKY1_PIN_SIZE, reg_val);
return 0;
}
static int sky1_pconf_parse_conf(struct pinctrl_dev *pctldev,
unsigned int pin, enum pin_config_param param,
enum pin_config_param arg)
{
int ret = 0;
struct sky1_pinctrl *spctl = pinctrl_dev_get_drvdata(pctldev);
switch (param) {
case PIN_CONFIG_BIAS_DISABLE:
ret = sky1_pconf_set_pull_select(spctl, pin, false, false);
break;
case PIN_CONFIG_BIAS_PULL_UP:
ret = sky1_pconf_set_pull_select(spctl, pin, true, true);
break;
case PIN_CONFIG_BIAS_PULL_DOWN:
ret = sky1_pconf_set_pull_select(spctl, pin, true, false);
break;
case PIN_CONFIG_DRIVE_STRENGTH:
ret = sky1_pconf_set_driving(spctl, pin, arg);
break;
default:
ret = -EINVAL;
}
return ret;
}
static int sky1_pconf_group_get(struct pinctrl_dev *pctldev,
unsigned int group,
unsigned long *config)
{
struct sky1_pinctrl *spctl = pinctrl_dev_get_drvdata(pctldev);
struct sky1_pinctrl_group *g = &spctl->groups[group];
*config = g->config;
return 0;
}
static int sky1_pconf_group_set(struct pinctrl_dev *pctldev, unsigned int group,
unsigned long *configs, unsigned int num_configs)
{
struct sky1_pinctrl *spctl = pinctrl_dev_get_drvdata(pctldev);
struct sky1_pinctrl_group *g = &spctl->groups[group];
int i, ret;
for (i = 0; i < num_configs; i++) {
ret = sky1_pconf_parse_conf(pctldev, g->pin,
pinconf_to_config_param(configs[i]),
pinconf_to_config_argument(configs[i]));
if (ret < 0)
return ret;
g->config = configs[i];
}
return 0;
}
static const struct pinconf_ops sky1_pinconf_ops = {
.pin_config_group_get = sky1_pconf_group_get,
.pin_config_group_set = sky1_pconf_group_set,
};
static int sky1_pctrl_build_state(struct platform_device *pdev)
{
struct sky1_pinctrl *spctl = platform_get_drvdata(pdev);
const struct sky1_pinctrl_soc_info *info = spctl->info;
int i;
/* Allocate groups */
spctl->groups = devm_kcalloc(&pdev->dev, info->npins,
sizeof(*spctl->groups), GFP_KERNEL);
if (!spctl->groups)
return -ENOMEM;
/* We assume that one pin is one group, use pin name as group name. */
spctl->grp_names = devm_kcalloc(&pdev->dev, info->npins,
sizeof(*spctl->grp_names), GFP_KERNEL);
if (!spctl->grp_names)
return -ENOMEM;
for (i = 0; i < info->npins; i++) {
const struct sky1_pin_desc *pin = spctl->info->pins + i;
struct sky1_pinctrl_group *group =
(struct sky1_pinctrl_group *)spctl->groups + i;
group->name = pin->pin.name;
group->pin = pin->pin.number;
spctl->grp_names[i] = pin->pin.name;
}
return 0;
}
int sky1_base_pinctrl_probe(struct platform_device *pdev,
const struct sky1_pinctrl_soc_info *info)
{
struct pinctrl_desc *sky1_pinctrl_desc;
struct sky1_pinctrl *spctl;
struct pinctrl_pin_desc *pins;
int ret, i;
if (!info || !info->pins || !info->npins) {
dev_err(&pdev->dev, "wrong pinctrl info\n");
return -EINVAL;
}
/* Create state holders etc for this driver */
spctl = devm_kzalloc(&pdev->dev, sizeof(*spctl), GFP_KERNEL);
if (!spctl)
return -ENOMEM;
spctl->info = info;
platform_set_drvdata(pdev, spctl);
spctl->base = devm_platform_ioremap_resource(pdev, 0);
if (IS_ERR(spctl->base))
return PTR_ERR(spctl->base);
sky1_pinctrl_desc = devm_kzalloc(&pdev->dev, sizeof(*sky1_pinctrl_desc),
GFP_KERNEL);
if (!sky1_pinctrl_desc)
return -ENOMEM;
pins = devm_kcalloc(&pdev->dev, info->npins, sizeof(*pins),
GFP_KERNEL);
if (!pins)
return -ENOMEM;
for (i = 0; i < info->npins; i++)
pins[i] = info->pins[i].pin;
ret = sky1_pctrl_build_state(pdev);
if (ret)
return ret;
sky1_pinctrl_desc->name = dev_name(&pdev->dev);
sky1_pinctrl_desc->pins = pins;
sky1_pinctrl_desc->npins = info->npins;
sky1_pinctrl_desc->pctlops = &sky1_pctrl_ops;
sky1_pinctrl_desc->pmxops = &sky1_pmx_ops;
sky1_pinctrl_desc->confops = &sky1_pinconf_ops;
sky1_pinctrl_desc->owner = THIS_MODULE;
spctl->dev = &pdev->dev;
ret = devm_pinctrl_register_and_init(&pdev->dev,
sky1_pinctrl_desc, spctl,
&spctl->pctl);
if (ret) {
dev_err(&pdev->dev, "could not register SKY1 pinctrl driver\n");
return ret;
}
/*
* The SKY1 SoC has two pin controllers: one for normal working state
* and one for sleep state. Since one controller only has working
* states and the other only sleep states, it will seem to the
* controller is always in the first configured state, so no
* transitions between default->sleep->default are detected and no
* new pin states are applied when we go in and out of sleep state.
*
* To counter this, provide dummies, so that the sleep-only pin
* controller still get some default states, and the working state pin
* controller get some sleep states, so that state transitions occur
* and we re-configure pins for default and sleep states.
*/
pinctrl_provide_dummies();
dev_dbg(&pdev->dev, "initialized SKY1 pinctrl driver\n");
return pinctrl_enable(spctl->pctl);
}
EXPORT_SYMBOL_GPL(sky1_base_pinctrl_probe);
MODULE_AUTHOR("Jerry Zhu <Jerry.Zhu@cixtech.com>");
MODULE_DESCRIPTION("Cix SKy1 pinctrl base driver");
MODULE_LICENSE("GPL");