| // SPDX-License-Identifier: GPL-2.0+ |
| /* |
| * Copyright (C) 2015-2016 Socionext Inc. |
| * Author: Masahiro Yamada <yamada.masahiro@socionext.com> |
| */ |
| |
| #include <common.h> |
| #include <dm.h> |
| #include <linux/io.h> |
| #include <linux/err.h> |
| #include <linux/kernel.h> |
| #include <linux/sizes.h> |
| #include <dm/pinctrl.h> |
| |
| #include "pinctrl-uniphier.h" |
| |
| #define UNIPHIER_PINCTRL_PINMUX_BASE 0x1000 |
| #define UNIPHIER_PINCTRL_LOAD_PINMUX 0x1700 |
| #define UNIPHIER_PINCTRL_PUPDCTRL_BASE 0x1a00 |
| #define UNIPHIER_PINCTRL_IECTRL 0x1d00 |
| |
| static const char *uniphier_pinctrl_dummy_name = "_dummy"; |
| |
| static int uniphier_pinctrl_get_groups_count(struct udevice *dev) |
| { |
| struct uniphier_pinctrl_priv *priv = dev_get_priv(dev); |
| |
| return priv->socdata->groups_count; |
| } |
| |
| static const char *uniphier_pinctrl_get_group_name(struct udevice *dev, |
| unsigned selector) |
| { |
| struct uniphier_pinctrl_priv *priv = dev_get_priv(dev); |
| |
| if (!priv->socdata->groups[selector].name) |
| return uniphier_pinctrl_dummy_name; |
| |
| return priv->socdata->groups[selector].name; |
| } |
| |
| static int uniphier_pinmux_get_functions_count(struct udevice *dev) |
| { |
| struct uniphier_pinctrl_priv *priv = dev_get_priv(dev); |
| |
| return priv->socdata->functions_count; |
| } |
| |
| static const char *uniphier_pinmux_get_function_name(struct udevice *dev, |
| unsigned selector) |
| { |
| struct uniphier_pinctrl_priv *priv = dev_get_priv(dev); |
| |
| if (!priv->socdata->functions[selector]) |
| return uniphier_pinctrl_dummy_name; |
| |
| return priv->socdata->functions[selector]; |
| } |
| |
| static int uniphier_pinconf_input_enable_perpin(struct udevice *dev, |
| unsigned int pin, int enable) |
| { |
| struct uniphier_pinctrl_priv *priv = dev_get_priv(dev); |
| unsigned reg; |
| u32 mask, tmp; |
| |
| reg = UNIPHIER_PINCTRL_IECTRL + pin / 32 * 4; |
| mask = BIT(pin % 32); |
| |
| tmp = readl(priv->base + reg); |
| if (enable) |
| tmp |= mask; |
| else |
| tmp &= ~mask; |
| writel(tmp, priv->base + reg); |
| |
| return 0; |
| } |
| |
| static int uniphier_pinconf_input_enable_legacy(struct udevice *dev, |
| unsigned int pin, int enable) |
| { |
| struct uniphier_pinctrl_priv *priv = dev_get_priv(dev); |
| |
| /* |
| * Multiple pins share one input enable, per-pin disabling is |
| * impossible. |
| */ |
| if (!enable) |
| return -EINVAL; |
| |
| /* Set all bits instead of having a bunch of pin data */ |
| writel(U32_MAX, priv->base + UNIPHIER_PINCTRL_IECTRL); |
| |
| return 0; |
| } |
| |
| static int uniphier_pinconf_input_enable(struct udevice *dev, |
| unsigned int pin, int enable) |
| { |
| struct uniphier_pinctrl_priv *priv = dev_get_priv(dev); |
| |
| if (priv->socdata->caps & UNIPHIER_PINCTRL_CAPS_PERPIN_IECTRL) |
| return uniphier_pinconf_input_enable_perpin(dev, pin, enable); |
| else |
| return uniphier_pinconf_input_enable_legacy(dev, pin, enable); |
| } |
| |
| #if CONFIG_IS_ENABLED(PINCONF) |
| |
| static const struct pinconf_param uniphier_pinconf_params[] = { |
| { "bias-disable", PIN_CONFIG_BIAS_DISABLE, 0 }, |
| { "bias-pull-up", PIN_CONFIG_BIAS_PULL_UP, 1 }, |
| { "bias-pull-down", PIN_CONFIG_BIAS_PULL_DOWN, 1 }, |
| { "bias-pull-pin-default", PIN_CONFIG_BIAS_PULL_PIN_DEFAULT, 1 }, |
| { "input-enable", PIN_CONFIG_INPUT_ENABLE, 1 }, |
| { "input-disable", PIN_CONFIG_INPUT_ENABLE, 0 }, |
| }; |
| |
| static int uniphier_pinconf_bias_set(struct udevice *dev, unsigned int pin, |
| unsigned int param, unsigned int arg) |
| { |
| struct uniphier_pinctrl_priv *priv = dev_get_priv(dev); |
| unsigned int enable = 1; |
| unsigned int reg; |
| u32 mask, tmp; |
| |
| if (!(priv->socdata->caps & UNIPHIER_PINCTRL_CAPS_PUPD_SIMPLE)) |
| return -ENOTSUPP; |
| |
| switch (param) { |
| case PIN_CONFIG_BIAS_DISABLE: |
| enable = 0; |
| break; |
| case PIN_CONFIG_BIAS_PULL_UP: |
| case PIN_CONFIG_BIAS_PULL_DOWN: |
| if (arg == 0) /* total bias is not supported */ |
| return -EINVAL; |
| break; |
| case PIN_CONFIG_BIAS_PULL_PIN_DEFAULT: |
| if (arg == 0) /* configuration ignored */ |
| return 0; |
| default: |
| BUG(); |
| } |
| |
| reg = UNIPHIER_PINCTRL_PUPDCTRL_BASE + pin / 32 * 4; |
| mask = BIT(pin % 32); |
| |
| tmp = readl(priv->base + reg); |
| if (enable) |
| tmp |= mask; |
| else |
| tmp &= ~mask; |
| writel(tmp, priv->base + reg); |
| |
| return 0; |
| } |
| |
| static int uniphier_pinconf_set_one(struct udevice *dev, unsigned int pin, |
| unsigned int param, unsigned int arg) |
| { |
| int ret; |
| |
| switch (param) { |
| case PIN_CONFIG_BIAS_DISABLE: |
| case PIN_CONFIG_BIAS_PULL_UP: |
| case PIN_CONFIG_BIAS_PULL_DOWN: |
| case PIN_CONFIG_BIAS_PULL_PIN_DEFAULT: |
| ret = uniphier_pinconf_bias_set(dev, pin, param, arg); |
| break; |
| case PIN_CONFIG_INPUT_ENABLE: |
| ret = uniphier_pinconf_input_enable(dev, pin, arg); |
| break; |
| default: |
| printf("unsupported configuration parameter %u\n", param); |
| return -EINVAL; |
| } |
| |
| return ret; |
| } |
| |
| static int uniphier_pinconf_group_set(struct udevice *dev, |
| unsigned int group_selector, |
| unsigned int param, unsigned int arg) |
| { |
| struct uniphier_pinctrl_priv *priv = dev_get_priv(dev); |
| const struct uniphier_pinctrl_group *grp = |
| &priv->socdata->groups[group_selector]; |
| int i, ret; |
| |
| for (i = 0; i < grp->num_pins; i++) { |
| ret = uniphier_pinconf_set_one(dev, grp->pins[i], param, arg); |
| if (ret) |
| return ret; |
| } |
| |
| return 0; |
| } |
| |
| #endif /* CONFIG_IS_ENABLED(PINCONF) */ |
| |
| static void uniphier_pinmux_set_one(struct udevice *dev, unsigned pin, |
| int muxval) |
| { |
| struct uniphier_pinctrl_priv *priv = dev_get_priv(dev); |
| unsigned reg, reg_end, shift, mask; |
| unsigned mux_bits = 8; |
| unsigned reg_stride = 4; |
| bool load_pinctrl = false; |
| u32 tmp; |
| |
| /* some pins need input-enabling */ |
| uniphier_pinconf_input_enable(dev, pin, 1); |
| |
| if (muxval < 0) |
| return; /* dedicated pin; nothing to do for pin-mux */ |
| |
| if (priv->socdata->caps & UNIPHIER_PINCTRL_CAPS_MUX_4BIT) |
| mux_bits = 4; |
| |
| if (priv->socdata->caps & UNIPHIER_PINCTRL_CAPS_DBGMUX_SEPARATE) { |
| /* |
| * Mode offset bit |
| * Normal 4 * n shift+3:shift |
| * Debug 4 * n shift+7:shift+4 |
| */ |
| mux_bits /= 2; |
| reg_stride = 8; |
| load_pinctrl = true; |
| } |
| |
| reg = UNIPHIER_PINCTRL_PINMUX_BASE + pin * mux_bits / 32 * reg_stride; |
| reg_end = reg + reg_stride; |
| shift = pin * mux_bits % 32; |
| mask = (1U << mux_bits) - 1; |
| |
| /* |
| * If reg_stride is greater than 4, the MSB of each pinsel shall be |
| * stored in the offset+4. |
| */ |
| for (; reg < reg_end; reg += 4) { |
| tmp = readl(priv->base + reg); |
| tmp &= ~(mask << shift); |
| tmp |= (mask & muxval) << shift; |
| writel(tmp, priv->base + reg); |
| |
| muxval >>= mux_bits; |
| } |
| |
| if (load_pinctrl) |
| writel(1, priv->base + UNIPHIER_PINCTRL_LOAD_PINMUX); |
| } |
| |
| static int uniphier_pinmux_group_set(struct udevice *dev, |
| unsigned group_selector, |
| unsigned func_selector) |
| { |
| struct uniphier_pinctrl_priv *priv = dev_get_priv(dev); |
| const struct uniphier_pinctrl_group *grp = |
| &priv->socdata->groups[group_selector]; |
| int i; |
| |
| for (i = 0; i < grp->num_pins; i++) |
| uniphier_pinmux_set_one(dev, grp->pins[i], grp->muxvals[i]); |
| |
| return 0; |
| } |
| |
| const struct pinctrl_ops uniphier_pinctrl_ops = { |
| .get_groups_count = uniphier_pinctrl_get_groups_count, |
| .get_group_name = uniphier_pinctrl_get_group_name, |
| .get_functions_count = uniphier_pinmux_get_functions_count, |
| .get_function_name = uniphier_pinmux_get_function_name, |
| .pinmux_group_set = uniphier_pinmux_group_set, |
| #if CONFIG_IS_ENABLED(PINCONF) |
| .pinconf_num_params = ARRAY_SIZE(uniphier_pinconf_params), |
| .pinconf_params = uniphier_pinconf_params, |
| .pinconf_group_set = uniphier_pinconf_group_set, |
| #endif |
| .set_state = pinctrl_generic_set_state, |
| }; |
| |
| int uniphier_pinctrl_probe(struct udevice *dev, |
| struct uniphier_pinctrl_socdata *socdata) |
| { |
| struct uniphier_pinctrl_priv *priv = dev_get_priv(dev); |
| fdt_addr_t addr; |
| |
| addr = devfdt_get_addr(dev->parent); |
| if (addr == FDT_ADDR_T_NONE) |
| return -EINVAL; |
| |
| priv->base = devm_ioremap(dev, addr, SZ_4K); |
| if (!priv->base) |
| return -ENOMEM; |
| |
| priv->socdata = socdata; |
| |
| return 0; |
| } |