root/drivers/pinctrl/sprd/pinctrl-sprd.c
// SPDX-License-Identifier: GPL-2.0-only
/*
 * Spreadtrum pin controller driver
 * Copyright (C) 2017 Spreadtrum  - http://www.spreadtrum.com
 */

#include <linux/debugfs.h>
#include <linux/err.h>
#include <linux/init.h>
#include <linux/io.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/platform_device.h>
#include <linux/seq_file.h>
#include <linux/slab.h>

#include <linux/pinctrl/consumer.h>
#include <linux/pinctrl/machine.h>
#include <linux/pinctrl/pinconf-generic.h>
#include <linux/pinctrl/pinconf.h>
#include <linux/pinctrl/pinctrl.h>
#include <linux/pinctrl/pinmux.h>

#include "../core.h"
#include "../pinmux.h"
#include "../pinconf.h"
#include "../pinctrl-utils.h"
#include "pinctrl-sprd.h"

#define PINCTRL_BIT_MASK(width)         (~(~0UL << (width)))
#define PINCTRL_REG_OFFSET              0x20
#define PINCTRL_REG_MISC_OFFSET         0x4020
#define PINCTRL_REG_LEN                 0x4

#define PIN_FUNC_MASK                   (BIT(4) | BIT(5))
#define PIN_FUNC_SEL_1                  ~PIN_FUNC_MASK
#define PIN_FUNC_SEL_2                  BIT(4)
#define PIN_FUNC_SEL_3                  BIT(5)
#define PIN_FUNC_SEL_4                  PIN_FUNC_MASK

#define AP_SLEEP_MODE                   BIT(13)
#define PUBCP_SLEEP_MODE                BIT(14)
#define TGLDSP_SLEEP_MODE               BIT(15)
#define AGDSP_SLEEP_MODE                BIT(16)
#define CM4_SLEEP_MODE                  BIT(17)
#define SLEEP_MODE_MASK                 GENMASK(5, 0)
#define SLEEP_MODE_SHIFT                13

#define SLEEP_INPUT                     BIT(1)
#define SLEEP_INPUT_MASK                0x1
#define SLEEP_INPUT_SHIFT               1

#define SLEEP_OUTPUT                    BIT(0)
#define SLEEP_OUTPUT_MASK               0x1
#define SLEEP_OUTPUT_SHIFT              0

#define DRIVE_STRENGTH_MASK             GENMASK(3, 0)
#define DRIVE_STRENGTH_SHIFT            19

#define SLEEP_PULL_DOWN                 BIT(2)
#define SLEEP_PULL_DOWN_MASK            0x1
#define SLEEP_PULL_DOWN_SHIFT           2

#define PULL_DOWN                       BIT(6)
#define PULL_DOWN_MASK                  0x1
#define PULL_DOWN_SHIFT                 6

#define SLEEP_PULL_UP                   BIT(3)
#define SLEEP_PULL_UP_MASK              0x1
#define SLEEP_PULL_UP_SHIFT             3

#define PULL_UP_4_7K                    (BIT(12) | BIT(7))
#define PULL_UP_20K                     BIT(7)
#define PULL_UP_MASK                    0x21
#define PULL_UP_SHIFT                   7

#define INPUT_SCHMITT                   BIT(11)
#define INPUT_SCHMITT_MASK              0x1
#define INPUT_SCHMITT_SHIFT             11

enum pin_sleep_mode {
        AP_SLEEP = BIT(0),
        PUBCP_SLEEP = BIT(1),
        TGLDSP_SLEEP = BIT(2),
        AGDSP_SLEEP = BIT(3),
        CM4_SLEEP = BIT(4),
};

enum pin_func_sel {
        PIN_FUNC_1,
        PIN_FUNC_2,
        PIN_FUNC_3,
        PIN_FUNC_4,
        PIN_FUNC_MAX,
};

/**
 * struct sprd_pin: represent one pin's description
 * @name: pin name
 * @number: pin number
 * @type: pin type, can be GLOBAL_CTRL_PIN/COMMON_PIN/MISC_PIN
 * @reg: pin register address
 * @bit_offset: bit offset in pin register
 * @bit_width: bit width in pin register
 */
struct sprd_pin {
        const char *name;
        unsigned int number;
        enum pin_type type;
        unsigned long reg;
        unsigned long bit_offset;
        unsigned long bit_width;
};

/**
 * struct sprd_pin_group: represent one group's description
 * @name: group name
 * @npins: pin numbers of this group
 * @pins: pointer to pins array
 */
struct sprd_pin_group {
        const char *name;
        unsigned int npins;
        unsigned int *pins;
};

/**
 * struct sprd_pinctrl_soc_info: represent the SoC's pins description
 * @groups: pointer to groups of pins
 * @ngroups: group numbers of the whole SoC
 * @pins: pointer to pins description
 * @npins: pin numbers of the whole SoC
 * @grp_names: pointer to group names array
 */
struct sprd_pinctrl_soc_info {
        struct sprd_pin_group *groups;
        unsigned int ngroups;
        struct sprd_pin *pins;
        unsigned int npins;
        const char **grp_names;
};

/**
 * struct sprd_pinctrl: represent the pin controller device
 * @dev: pointer to the device structure
 * @pctl: pointer to the pinctrl handle
 * @base: base address of the controller
 * @info: pointer to SoC's pins description information
 */
struct sprd_pinctrl {
        struct device *dev;
        struct pinctrl_dev *pctl;
        void __iomem *base;
        struct sprd_pinctrl_soc_info *info;
};

#define SPRD_PIN_CONFIG_CONTROL         (PIN_CONFIG_END + 1)
#define SPRD_PIN_CONFIG_SLEEP_MODE      (PIN_CONFIG_END + 2)

static int sprd_pinctrl_get_id_by_name(struct sprd_pinctrl *sprd_pctl,
                                       const char *name)
{
        struct sprd_pinctrl_soc_info *info = sprd_pctl->info;
        int i;

        for (i = 0; i < info->npins; i++) {
                if (!strcmp(info->pins[i].name, name))
                        return info->pins[i].number;
        }

        return -ENODEV;
}

static struct sprd_pin *
sprd_pinctrl_get_pin_by_id(struct sprd_pinctrl *sprd_pctl, unsigned int id)
{
        struct sprd_pinctrl_soc_info *info = sprd_pctl->info;
        struct sprd_pin *pin = NULL;
        int i;

        for (i = 0; i < info->npins; i++) {
                if (info->pins[i].number == id) {
                        pin = &info->pins[i];
                        break;
                }
        }

        return pin;
}

static const struct sprd_pin_group *
sprd_pinctrl_find_group_by_name(struct sprd_pinctrl *sprd_pctl,
                                const char *name)
{
        struct sprd_pinctrl_soc_info *info = sprd_pctl->info;
        const struct sprd_pin_group *grp = NULL;
        int i;

        for (i = 0; i < info->ngroups; i++) {
                if (!strcmp(info->groups[i].name, name)) {
                        grp = &info->groups[i];
                        break;
                }
        }

        return grp;
}

static int sprd_pctrl_group_count(struct pinctrl_dev *pctldev)
{
        struct sprd_pinctrl *pctl = pinctrl_dev_get_drvdata(pctldev);
        struct sprd_pinctrl_soc_info *info = pctl->info;

        return info->ngroups;
}

static const char *sprd_pctrl_group_name(struct pinctrl_dev *pctldev,
                                         unsigned int selector)
{
        struct sprd_pinctrl *pctl = pinctrl_dev_get_drvdata(pctldev);
        struct sprd_pinctrl_soc_info *info = pctl->info;

        return info->groups[selector].name;
}

static int sprd_pctrl_group_pins(struct pinctrl_dev *pctldev,
                                 unsigned int selector,
                                 const unsigned int **pins,
                                 unsigned int *npins)
{
        struct sprd_pinctrl *pctl = pinctrl_dev_get_drvdata(pctldev);
        struct sprd_pinctrl_soc_info *info = pctl->info;

        if (selector >= info->ngroups)
                return -EINVAL;

        *pins = info->groups[selector].pins;
        *npins = info->groups[selector].npins;

        return 0;
}

static int sprd_dt_node_to_map(struct pinctrl_dev *pctldev,
                               struct device_node *np,
                               struct pinctrl_map **map,
                               unsigned int *num_maps)
{
        struct sprd_pinctrl *pctl = pinctrl_dev_get_drvdata(pctldev);
        const struct sprd_pin_group *grp;
        unsigned long *configs = NULL;
        unsigned int num_configs = 0;
        unsigned int reserved_maps = 0;
        unsigned int reserve = 0;
        const char *function;
        enum pinctrl_map_type type;
        int ret;

        grp = sprd_pinctrl_find_group_by_name(pctl, np->name);
        if (!grp) {
                dev_err(pctl->dev, "unable to find group for node %pOF\n", np);
                return -EINVAL;
        }

        ret = of_property_count_strings(np, "pins");
        if (ret < 0)
                return ret;

        if (ret == 1)
                type = PIN_MAP_TYPE_CONFIGS_PIN;
        else
                type = PIN_MAP_TYPE_CONFIGS_GROUP;

        ret = of_property_read_string(np, "function", &function);
        if (ret < 0) {
                if (ret != -EINVAL)
                        dev_err(pctl->dev,
                                "%pOF: could not parse property function\n", np);
                function = NULL;
        }

        ret = pinconf_generic_parse_dt_config(np, pctldev, &configs,
                                              &num_configs);
        if (ret < 0) {
                dev_err(pctl->dev, "%pOF: could not parse node property\n", np);
                return ret;
        }

        *map = NULL;
        *num_maps = 0;

        if (function != NULL)
                reserve++;
        if (num_configs)
                reserve++;

        ret = pinctrl_utils_reserve_map(pctldev, map, &reserved_maps,
                                        num_maps, reserve);
        if (ret < 0)
                goto out;

        if (function) {
                ret = pinctrl_utils_add_map_mux(pctldev, map,
                                                &reserved_maps, num_maps,
                                                grp->name, function);
                if (ret < 0)
                        goto out;
        }

        if (num_configs) {
                const char *group_or_pin;
                unsigned int pin_id;

                if (type == PIN_MAP_TYPE_CONFIGS_PIN) {
                        pin_id = grp->pins[0];
                        group_or_pin = pin_get_name(pctldev, pin_id);
                } else {
                        group_or_pin = grp->name;
                }

                ret = pinctrl_utils_add_map_configs(pctldev, map,
                                                    &reserved_maps, num_maps,
                                                    group_or_pin, configs,
                                                    num_configs, type);
        }

out:
        kfree(configs);
        return ret;
}

static void sprd_pctrl_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 sprd_pctrl_ops = {
        .get_groups_count = sprd_pctrl_group_count,
        .get_group_name = sprd_pctrl_group_name,
        .get_group_pins = sprd_pctrl_group_pins,
        .pin_dbg_show = sprd_pctrl_dbg_show,
        .dt_node_to_map = sprd_dt_node_to_map,
        .dt_free_map = pinctrl_utils_free_map,
};

static int sprd_pmx_get_function_count(struct pinctrl_dev *pctldev)
{
        return PIN_FUNC_MAX;
}

static const char *sprd_pmx_get_function_name(struct pinctrl_dev *pctldev,
                                              unsigned int selector)
{
        switch (selector) {
        case PIN_FUNC_1:
                return "func1";
        case PIN_FUNC_2:
                return "func2";
        case PIN_FUNC_3:
                return "func3";
        case PIN_FUNC_4:
                return "func4";
        default:
                return "null";
        }
}

static int sprd_pmx_get_function_groups(struct pinctrl_dev *pctldev,
                                        unsigned int selector,
                                        const char * const **groups,
                                        unsigned int * const num_groups)
{
        struct sprd_pinctrl *pctl = pinctrl_dev_get_drvdata(pctldev);
        struct sprd_pinctrl_soc_info *info = pctl->info;

        *groups = info->grp_names;
        *num_groups = info->ngroups;

        return 0;
}

static int sprd_pmx_set_mux(struct pinctrl_dev *pctldev,
                            unsigned int func_selector,
                            unsigned int group_selector)
{
        struct sprd_pinctrl *pctl = pinctrl_dev_get_drvdata(pctldev);
        struct sprd_pinctrl_soc_info *info = pctl->info;
        struct sprd_pin_group *grp = &info->groups[group_selector];
        unsigned int i, grp_pins = grp->npins;
        unsigned long reg;
        unsigned int val = 0;

        if (group_selector >= info->ngroups)
                return -EINVAL;

        switch (func_selector) {
        case PIN_FUNC_1:
                val &= PIN_FUNC_SEL_1;
                break;
        case PIN_FUNC_2:
                val |= PIN_FUNC_SEL_2;
                break;
        case PIN_FUNC_3:
                val |= PIN_FUNC_SEL_3;
                break;
        case PIN_FUNC_4:
                val |= PIN_FUNC_SEL_4;
                break;
        default:
                break;
        }

        for (i = 0; i < grp_pins; i++) {
                unsigned int pin_id = grp->pins[i];
                struct sprd_pin *pin = sprd_pinctrl_get_pin_by_id(pctl, pin_id);

                if (!pin || pin->type != COMMON_PIN)
                        continue;

                reg = readl((void __iomem *)pin->reg);
                reg &= ~PIN_FUNC_MASK;
                reg |= val;
                writel(reg, (void __iomem *)pin->reg);
        }

        return 0;
}

static const struct pinmux_ops sprd_pmx_ops = {
        .get_functions_count = sprd_pmx_get_function_count,
        .get_function_name = sprd_pmx_get_function_name,
        .get_function_groups = sprd_pmx_get_function_groups,
        .set_mux = sprd_pmx_set_mux,
};

static int sprd_pinconf_get(struct pinctrl_dev *pctldev, unsigned int pin_id,
                            unsigned long *config)
{
        struct sprd_pinctrl *pctl = pinctrl_dev_get_drvdata(pctldev);
        struct sprd_pin *pin = sprd_pinctrl_get_pin_by_id(pctl, pin_id);
        unsigned int param = pinconf_to_config_param(*config);
        unsigned int reg, arg;

        if (!pin)
                return -EINVAL;

        if (pin->type == GLOBAL_CTRL_PIN) {
                reg = (readl((void __iomem *)pin->reg) >>
                           pin->bit_offset) & PINCTRL_BIT_MASK(pin->bit_width);
        } else {
                reg = readl((void __iomem *)pin->reg);
        }

        if (pin->type == GLOBAL_CTRL_PIN &&
            param == SPRD_PIN_CONFIG_CONTROL) {
                arg = reg;
        } else if (pin->type == COMMON_PIN || pin->type == MISC_PIN) {
                switch (param) {
                case SPRD_PIN_CONFIG_SLEEP_MODE:
                        arg = (reg >> SLEEP_MODE_SHIFT) & SLEEP_MODE_MASK;
                        break;
                case PIN_CONFIG_INPUT_ENABLE:
                        arg = (reg >> SLEEP_INPUT_SHIFT) & SLEEP_INPUT_MASK;
                        break;
                case PIN_CONFIG_OUTPUT_ENABLE:
                        arg = reg & SLEEP_OUTPUT_MASK;
                        break;
                case PIN_CONFIG_BIAS_HIGH_IMPEDANCE:
                        if ((reg & SLEEP_OUTPUT) || (reg & SLEEP_INPUT))
                                return -EINVAL;

                        arg = 1;
                        break;
                case PIN_CONFIG_DRIVE_STRENGTH:
                        arg = (reg >> DRIVE_STRENGTH_SHIFT) &
                                DRIVE_STRENGTH_MASK;
                        break;
                case PIN_CONFIG_BIAS_PULL_DOWN:
                        /* combine sleep pull down and pull down config */
                        arg = ((reg >> SLEEP_PULL_DOWN_SHIFT) &
                               SLEEP_PULL_DOWN_MASK) << 16;
                        arg |= (reg >> PULL_DOWN_SHIFT) & PULL_DOWN_MASK;
                        break;
                case PIN_CONFIG_INPUT_SCHMITT_ENABLE:
                        arg = (reg >> INPUT_SCHMITT_SHIFT) & INPUT_SCHMITT_MASK;
                        break;
                case PIN_CONFIG_BIAS_PULL_UP:
                        /* combine sleep pull up and pull up config */
                        arg = ((reg >> SLEEP_PULL_UP_SHIFT) &
                               SLEEP_PULL_UP_MASK) << 16;
                        arg |= (reg >> PULL_UP_SHIFT) & PULL_UP_MASK;
                        break;
                case PIN_CONFIG_BIAS_DISABLE:
                        if ((reg & (SLEEP_PULL_DOWN | SLEEP_PULL_UP)) ||
                            (reg & (PULL_DOWN | PULL_UP_4_7K | PULL_UP_20K)))
                                return -EINVAL;

                        arg = 1;
                        break;
                case PIN_CONFIG_SLEEP_HARDWARE_STATE:
                        arg = 0;
                        break;
                default:
                        return -ENOTSUPP;
                }
        } else {
                return -ENOTSUPP;
        }

        *config = pinconf_to_config_packed(param, arg);
        return 0;
}

static unsigned int sprd_pinconf_drive(unsigned int mA)
{
        unsigned int val = 0;

        switch (mA) {
        case 2:
                break;
        case 4:
                val |= BIT(19);
                break;
        case 6:
                val |= BIT(20);
                break;
        case 8:
                val |= BIT(19) | BIT(20);
                break;
        case 10:
                val |= BIT(21);
                break;
        case 12:
                val |= BIT(21) | BIT(19);
                break;
        case 14:
                val |= BIT(21) | BIT(20);
                break;
        case 16:
                val |= BIT(19) | BIT(20) | BIT(21);
                break;
        case 20:
                val |= BIT(22);
                break;
        case 21:
                val |= BIT(22) | BIT(19);
                break;
        case 24:
                val |= BIT(22) | BIT(20);
                break;
        case 25:
                val |= BIT(22) | BIT(20) | BIT(19);
                break;
        case 27:
                val |= BIT(22) | BIT(21);
                break;
        case 29:
                val |= BIT(22) | BIT(21) | BIT(19);
                break;
        case 31:
                val |= BIT(22) | BIT(21) | BIT(20);
                break;
        case 33:
                val |= BIT(22) | BIT(21) | BIT(20) | BIT(19);
                break;
        default:
                break;
        }

        return val;
}

static bool sprd_pinctrl_check_sleep_config(unsigned long *configs,
                                            unsigned int num_configs)
{
        unsigned int param;
        int i;

        for (i = 0; i < num_configs; i++) {
                param = pinconf_to_config_param(configs[i]);
                if (param == PIN_CONFIG_SLEEP_HARDWARE_STATE)
                        return true;
        }

        return false;
}

static int sprd_pinconf_set(struct pinctrl_dev *pctldev, unsigned int pin_id,
                            unsigned long *configs, unsigned int num_configs)
{
        struct sprd_pinctrl *pctl = pinctrl_dev_get_drvdata(pctldev);
        struct sprd_pin *pin = sprd_pinctrl_get_pin_by_id(pctl, pin_id);
        bool is_sleep_config;
        unsigned long reg;
        int i;

        if (!pin)
                return -EINVAL;

        is_sleep_config = sprd_pinctrl_check_sleep_config(configs, num_configs);

        for (i = 0; i < num_configs; i++) {
                unsigned int param, arg, shift, mask, val;

                param = pinconf_to_config_param(configs[i]);
                arg = pinconf_to_config_argument(configs[i]);

                val = 0;
                shift = 0;
                mask = 0;
                if (pin->type == GLOBAL_CTRL_PIN &&
                    param == SPRD_PIN_CONFIG_CONTROL) {
                        val = arg;
                } else if (pin->type == COMMON_PIN || pin->type == MISC_PIN) {
                        switch (param) {
                        case SPRD_PIN_CONFIG_SLEEP_MODE:
                                if (arg & AP_SLEEP)
                                        val |= AP_SLEEP_MODE;
                                if (arg & PUBCP_SLEEP)
                                        val |= PUBCP_SLEEP_MODE;
                                if (arg & TGLDSP_SLEEP)
                                        val |= TGLDSP_SLEEP_MODE;
                                if (arg & AGDSP_SLEEP)
                                        val |= AGDSP_SLEEP_MODE;
                                if (arg & CM4_SLEEP)
                                        val |= CM4_SLEEP_MODE;

                                mask = SLEEP_MODE_MASK;
                                shift = SLEEP_MODE_SHIFT;
                                break;
                        case PIN_CONFIG_INPUT_ENABLE:
                                if (is_sleep_config == true) {
                                        if (arg > 0)
                                                val |= SLEEP_INPUT;
                                        else
                                                val &= ~SLEEP_INPUT;

                                        mask = SLEEP_INPUT_MASK;
                                        shift = SLEEP_INPUT_SHIFT;
                                }
                                break;
                        case PIN_CONFIG_OUTPUT_ENABLE:
                                if (is_sleep_config == true) {
                                        if (arg > 0)
                                                val |= SLEEP_OUTPUT;
                                        else
                                                val &= ~SLEEP_OUTPUT;

                                        mask = SLEEP_OUTPUT_MASK;
                                        shift = SLEEP_OUTPUT_SHIFT;
                                }
                                break;
                        case PIN_CONFIG_BIAS_HIGH_IMPEDANCE:
                                if (is_sleep_config == true) {
                                        val = shift = 0;
                                        mask = SLEEP_OUTPUT | SLEEP_INPUT;
                                }
                                break;
                        case PIN_CONFIG_DRIVE_STRENGTH:
                                if (arg < 2 || arg > 60)
                                        return -EINVAL;

                                val = sprd_pinconf_drive(arg);
                                mask = DRIVE_STRENGTH_MASK;
                                shift = DRIVE_STRENGTH_SHIFT;
                                break;
                        case PIN_CONFIG_BIAS_PULL_DOWN:
                                if (is_sleep_config == true) {
                                        val |= SLEEP_PULL_DOWN;
                                        mask = SLEEP_PULL_DOWN_MASK;
                                        shift = SLEEP_PULL_DOWN_SHIFT;
                                } else {
                                        val |= PULL_DOWN;
                                        mask = PULL_DOWN_MASK;
                                        shift = PULL_DOWN_SHIFT;
                                }
                                break;
                        case PIN_CONFIG_INPUT_SCHMITT_ENABLE:
                                if (arg > 0)
                                        val |= INPUT_SCHMITT;
                                else
                                        val &= ~INPUT_SCHMITT;

                                mask = INPUT_SCHMITT_MASK;
                                shift = INPUT_SCHMITT_SHIFT;
                                break;
                        case PIN_CONFIG_BIAS_PULL_UP:
                                if (is_sleep_config) {
                                        val |= SLEEP_PULL_UP;
                                        mask = SLEEP_PULL_UP_MASK;
                                        shift = SLEEP_PULL_UP_SHIFT;
                                } else {
                                        if (arg == 20000)
                                                val |= PULL_UP_20K;
                                        else if (arg == 4700)
                                                val |= PULL_UP_4_7K;

                                        mask = PULL_UP_MASK;
                                        shift = PULL_UP_SHIFT;
                                }
                                break;
                        case PIN_CONFIG_BIAS_DISABLE:
                                if (is_sleep_config == true) {
                                        val = shift = 0;
                                        mask = SLEEP_PULL_DOWN | SLEEP_PULL_UP;
                                } else {
                                        val = shift = 0;
                                        mask = PULL_DOWN | PULL_UP_20K |
                                                PULL_UP_4_7K;
                                }
                                break;
                        case PIN_CONFIG_SLEEP_HARDWARE_STATE:
                                continue;
                        default:
                                return -ENOTSUPP;
                        }
                } else {
                        return -ENOTSUPP;
                }

                if (pin->type == GLOBAL_CTRL_PIN) {
                        reg = readl((void __iomem *)pin->reg);
                        reg &= ~(PINCTRL_BIT_MASK(pin->bit_width)
                                << pin->bit_offset);
                        reg |= (val & PINCTRL_BIT_MASK(pin->bit_width))
                                << pin->bit_offset;
                        writel(reg, (void __iomem *)pin->reg);
                } else {
                        reg = readl((void __iomem *)pin->reg);
                        reg &= ~(mask << shift);
                        reg |= val;
                        writel(reg, (void __iomem *)pin->reg);
                }
        }

        return 0;
}

static int sprd_pinconf_group_get(struct pinctrl_dev *pctldev,
                                  unsigned int selector, unsigned long *config)
{
        struct sprd_pinctrl *pctl = pinctrl_dev_get_drvdata(pctldev);
        struct sprd_pinctrl_soc_info *info = pctl->info;
        struct sprd_pin_group *grp;
        unsigned int pin_id;

        if (selector >= info->ngroups)
                return -EINVAL;

        grp = &info->groups[selector];
        pin_id = grp->pins[0];

        return sprd_pinconf_get(pctldev, pin_id, config);
}

static int sprd_pinconf_group_set(struct pinctrl_dev *pctldev,
                                  unsigned int selector,
                                  unsigned long *configs,
                                  unsigned int num_configs)
{
        struct sprd_pinctrl *pctl = pinctrl_dev_get_drvdata(pctldev);
        struct sprd_pinctrl_soc_info *info = pctl->info;
        struct sprd_pin_group *grp;
        int ret, i;

        if (selector >= info->ngroups)
                return -EINVAL;

        grp = &info->groups[selector];

        for (i = 0; i < grp->npins; i++) {
                unsigned int pin_id = grp->pins[i];

                ret = sprd_pinconf_set(pctldev, pin_id, configs, num_configs);
                if (ret)
                        return ret;
        }

        return 0;
}

static int sprd_pinconf_get_config(struct pinctrl_dev *pctldev,
                                   unsigned int pin_id,
                                   unsigned long *config)
{
        struct sprd_pinctrl *pctl = pinctrl_dev_get_drvdata(pctldev);
        struct sprd_pin *pin = sprd_pinctrl_get_pin_by_id(pctl, pin_id);

        if (!pin)
                return -EINVAL;

        if (pin->type == GLOBAL_CTRL_PIN) {
                *config = (readl((void __iomem *)pin->reg) >>
                           pin->bit_offset) & PINCTRL_BIT_MASK(pin->bit_width);
        } else {
                *config = readl((void __iomem *)pin->reg);
        }

        return 0;
}

static void sprd_pinconf_dbg_show(struct pinctrl_dev *pctldev,
                                  struct seq_file *s, unsigned int pin_id)
{
        unsigned long config;
        int ret;

        ret = sprd_pinconf_get_config(pctldev, pin_id, &config);
        if (ret)
                return;

        seq_printf(s, "0x%lx", config);
}

static void sprd_pinconf_group_dbg_show(struct pinctrl_dev *pctldev,
                                        struct seq_file *s,
                                        unsigned int selector)
{
        struct sprd_pinctrl *pctl = pinctrl_dev_get_drvdata(pctldev);
        struct sprd_pinctrl_soc_info *info = pctl->info;
        struct sprd_pin_group *grp;
        unsigned long config;
        const char *name;
        int i, ret;

        if (selector >= info->ngroups)
                return;

        grp = &info->groups[selector];

        seq_putc(s, '\n');
        for (i = 0; i < grp->npins; i++, config++) {
                unsigned int pin_id = grp->pins[i];

                name = pin_get_name(pctldev, pin_id);
                ret = sprd_pinconf_get_config(pctldev, pin_id, &config);
                if (ret)
                        return;

                seq_printf(s, "%s: 0x%lx ", name, config);
        }
}

static const struct pinconf_ops sprd_pinconf_ops = {
        .is_generic = true,
        .pin_config_get = sprd_pinconf_get,
        .pin_config_set = sprd_pinconf_set,
        .pin_config_group_get = sprd_pinconf_group_get,
        .pin_config_group_set = sprd_pinconf_group_set,
        .pin_config_dbg_show = sprd_pinconf_dbg_show,
        .pin_config_group_dbg_show = sprd_pinconf_group_dbg_show,
};

static const struct pinconf_generic_params sprd_dt_params[] = {
        {"sprd,control", SPRD_PIN_CONFIG_CONTROL, 0},
        {"sprd,sleep-mode", SPRD_PIN_CONFIG_SLEEP_MODE, 0},
};

#ifdef CONFIG_DEBUG_FS
static const struct pin_config_item sprd_conf_items[] = {
        PCONFDUMP(SPRD_PIN_CONFIG_CONTROL, "global control", NULL, true),
        PCONFDUMP(SPRD_PIN_CONFIG_SLEEP_MODE, "sleep mode", NULL, true),
};
#endif

static struct pinctrl_desc sprd_pinctrl_desc = {
        .pctlops = &sprd_pctrl_ops,
        .pmxops = &sprd_pmx_ops,
        .confops = &sprd_pinconf_ops,
        .num_custom_params = ARRAY_SIZE(sprd_dt_params),
        .custom_params = sprd_dt_params,
#ifdef CONFIG_DEBUG_FS
        .custom_conf_items = sprd_conf_items,
#endif
        .owner = THIS_MODULE,
};

static int sprd_pinctrl_parse_groups(struct device_node *np,
                                     struct sprd_pinctrl *sprd_pctl,
                                     struct sprd_pin_group *grp)
{
        struct property *prop;
        const char *pin_name;
        int ret, i = 0;

        ret = of_property_count_strings(np, "pins");
        if (ret < 0)
                return ret;

        grp->name = np->name;
        grp->npins = ret;
        grp->pins = devm_kcalloc(sprd_pctl->dev,
                                 grp->npins, sizeof(unsigned int),
                                 GFP_KERNEL);
        if (!grp->pins)
                return -ENOMEM;

        of_property_for_each_string(np, "pins", prop, pin_name) {
                ret = sprd_pinctrl_get_id_by_name(sprd_pctl, pin_name);
                if (ret >= 0)
                        grp->pins[i++] = ret;
        }

        for (i = 0; i < grp->npins; i++) {
                dev_dbg(sprd_pctl->dev,
                        "Group[%s] contains [%d] pins: id = %d\n",
                        grp->name, grp->npins, grp->pins[i]);
        }

        return 0;
}

static unsigned int sprd_pinctrl_get_groups(struct device_node *np)
{
        struct device_node *child;
        unsigned int group_cnt, cnt;

        group_cnt = of_get_child_count(np);

        for_each_child_of_node(np, child) {
                cnt = of_get_child_count(child);
                if (cnt > 0)
                        group_cnt += cnt;
        }

        return group_cnt;
}

static int sprd_pinctrl_parse_dt(struct sprd_pinctrl *sprd_pctl)
{
        struct sprd_pinctrl_soc_info *info = sprd_pctl->info;
        struct device_node *np = sprd_pctl->dev->of_node;
        struct sprd_pin_group *grp;
        const char **temp;
        int ret;

        if (!np)
                return -ENODEV;

        info->ngroups = sprd_pinctrl_get_groups(np);
        if (!info->ngroups)
                return 0;

        info->groups = devm_kcalloc(sprd_pctl->dev,
                                    info->ngroups,
                                    sizeof(struct sprd_pin_group),
                                    GFP_KERNEL);
        if (!info->groups)
                return -ENOMEM;

        info->grp_names = devm_kcalloc(sprd_pctl->dev,
                                       info->ngroups, sizeof(char *),
                                       GFP_KERNEL);
        if (!info->grp_names)
                return -ENOMEM;

        temp = info->grp_names;
        grp = info->groups;

        for_each_child_of_node_scoped(np, child) {
                ret = sprd_pinctrl_parse_groups(child, sprd_pctl, grp);
                if (ret)
                        return ret;

                *temp++ = grp->name;
                grp++;

                if (of_get_child_count(child) > 0) {
                        for_each_child_of_node_scoped(child, sub_child) {
                                ret = sprd_pinctrl_parse_groups(sub_child,
                                                                sprd_pctl, grp);
                                if (ret)
                                        return ret;

                                *temp++ = grp->name;
                                grp++;
                        }
                }
        }

        return 0;
}

static int sprd_pinctrl_add_pins(struct sprd_pinctrl *sprd_pctl,
                                 struct sprd_pins_info *sprd_soc_pin_info,
                                 int pins_cnt)
{
        struct sprd_pinctrl_soc_info *info = sprd_pctl->info;
        unsigned int ctrl_pin = 0, com_pin = 0;
        struct sprd_pin *pin;
        int i;

        info->npins = pins_cnt;
        info->pins = devm_kcalloc(sprd_pctl->dev,
                                  info->npins, sizeof(struct sprd_pin),
                                  GFP_KERNEL);
        if (!info->pins)
                return -ENOMEM;

        for (i = 0, pin = info->pins; i < info->npins; i++, pin++) {
                unsigned int reg;

                pin->name = sprd_soc_pin_info[i].name;
                pin->type = sprd_soc_pin_info[i].type;
                pin->number = sprd_soc_pin_info[i].num;
                reg = sprd_soc_pin_info[i].reg;
                if (pin->type == GLOBAL_CTRL_PIN) {
                        pin->reg = (unsigned long)sprd_pctl->base +
                                PINCTRL_REG_LEN * reg;
                        pin->bit_offset = sprd_soc_pin_info[i].bit_offset;
                        pin->bit_width = sprd_soc_pin_info[i].bit_width;
                        ctrl_pin++;
                } else if (pin->type == COMMON_PIN) {
                        pin->reg = (unsigned long)sprd_pctl->base +
                                PINCTRL_REG_OFFSET + PINCTRL_REG_LEN *
                                (i - ctrl_pin);
                        com_pin++;
                } else if (pin->type == MISC_PIN) {
                        pin->reg = (unsigned long)sprd_pctl->base +
                                PINCTRL_REG_MISC_OFFSET + PINCTRL_REG_LEN *
                                (i - ctrl_pin - com_pin);
                }
        }

        for (i = 0, pin = info->pins; i < info->npins; pin++, i++) {
                dev_dbg(sprd_pctl->dev, "pin name[%s-%d], type = %d, "
                        "bit offset = %ld, bit width = %ld, reg = 0x%lx\n",
                        pin->name, pin->number, pin->type,
                        pin->bit_offset, pin->bit_width, pin->reg);
        }

        return 0;
}

int sprd_pinctrl_core_probe(struct platform_device *pdev,
                            struct sprd_pins_info *sprd_soc_pin_info,
                            int pins_cnt)
{
        struct sprd_pinctrl *sprd_pctl;
        struct sprd_pinctrl_soc_info *pinctrl_info;
        struct pinctrl_pin_desc *pin_desc;
        int ret, i;

        sprd_pctl = devm_kzalloc(&pdev->dev, sizeof(struct sprd_pinctrl),
                                 GFP_KERNEL);
        if (!sprd_pctl)
                return -ENOMEM;

        sprd_pctl->base = devm_platform_ioremap_resource(pdev, 0);
        if (IS_ERR(sprd_pctl->base))
                return PTR_ERR(sprd_pctl->base);

        pinctrl_info = devm_kzalloc(&pdev->dev,
                                    sizeof(struct sprd_pinctrl_soc_info),
                                    GFP_KERNEL);
        if (!pinctrl_info)
                return -ENOMEM;

        sprd_pctl->info = pinctrl_info;
        sprd_pctl->dev = &pdev->dev;
        platform_set_drvdata(pdev, sprd_pctl);

        ret = sprd_pinctrl_add_pins(sprd_pctl, sprd_soc_pin_info, pins_cnt);
        if (ret) {
                dev_err(&pdev->dev, "fail to add pins information\n");
                return ret;
        }

        ret = sprd_pinctrl_parse_dt(sprd_pctl);
        if (ret) {
                dev_err(&pdev->dev, "fail to parse dt properties\n");
                return ret;
        }

        pin_desc = devm_kcalloc(&pdev->dev,
                                pinctrl_info->npins,
                                sizeof(struct pinctrl_pin_desc),
                                GFP_KERNEL);
        if (!pin_desc)
                return -ENOMEM;

        for (i = 0; i < pinctrl_info->npins; i++) {
                pin_desc[i].number = pinctrl_info->pins[i].number;
                pin_desc[i].name = pinctrl_info->pins[i].name;
                pin_desc[i].drv_data = pinctrl_info;
        }

        sprd_pinctrl_desc.pins = pin_desc;
        sprd_pinctrl_desc.name = dev_name(&pdev->dev);
        sprd_pinctrl_desc.npins = pinctrl_info->npins;

        sprd_pctl->pctl = pinctrl_register(&sprd_pinctrl_desc,
                                           &pdev->dev, (void *)sprd_pctl);
        if (IS_ERR(sprd_pctl->pctl)) {
                dev_err(&pdev->dev, "could not register pinctrl driver\n");
                return PTR_ERR(sprd_pctl->pctl);
        }

        return 0;
}
EXPORT_SYMBOL_GPL(sprd_pinctrl_core_probe);

void sprd_pinctrl_remove(struct platform_device *pdev)
{
        struct sprd_pinctrl *sprd_pctl = platform_get_drvdata(pdev);

        pinctrl_unregister(sprd_pctl->pctl);
}
EXPORT_SYMBOL_GPL(sprd_pinctrl_remove);

void sprd_pinctrl_shutdown(struct platform_device *pdev)
{
        struct pinctrl *pinctl;
        struct pinctrl_state *state;

        pinctl = devm_pinctrl_get(&pdev->dev);
        if (IS_ERR(pinctl))
                return;
        state = pinctrl_lookup_state(pinctl, "shutdown");
        if (IS_ERR(state))
                return;
        pinctrl_select_state(pinctl, state);
}
EXPORT_SYMBOL_GPL(sprd_pinctrl_shutdown);

MODULE_DESCRIPTION("SPREADTRUM Pin Controller Driver");
MODULE_AUTHOR("Baolin Wang <baolin.wang@spreadtrum.com>");
MODULE_LICENSE("GPL v2");