| // 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"); |