root/drivers/pinctrl/cix/pinctrl-sky1-base.c
// 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");