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