]> git.ipfire.org Git - thirdparty/kernel/stable.git/commitdiff
pinctrl: renesas: rzt2h: Add pin configuration support
authorLad Prabhakar <prabhakar.mahadev-lad.rj@bp.renesas.com>
Thu, 19 Mar 2026 14:15:15 +0000 (14:15 +0000)
committerGeert Uytterhoeven <geert+renesas@glider.be>
Thu, 26 Mar 2026 19:04:28 +0000 (20:04 +0100)
Add pin configuration support for the Renesas RZ/T2H SoC. The RZ/T2H SoC
allows configuring several electrical characteristics through the DRCTLm
(I/O Buffer Function Switching) registers. These registers control bias
configuration, Schmitt trigger input, output slew rate, and drive
strength.

Implement pinconf_ops to allow reading and updating these properties
through the generic pin configuration framework. The implementation
supports bias-disable, bias-pull-up, bias-pull-down,
input-schmitt-enable, slew-rate, and drive-strength-microamp.

Signed-off-by: Lad Prabhakar <prabhakar.mahadev-lad.rj@bp.renesas.com>
Reviewed-by: Linus Walleij <linusw@kernel.org>
Reviewed-by: Geert Uytterhoeven <geert+renesas@glider.be>
Link: https://patch.msgid.link/20260319141515.2053556-3-prabhakar.mahadev-lad.rj@bp.renesas.com
Signed-off-by: Geert Uytterhoeven <geert+renesas@glider.be>
drivers/pinctrl/renesas/pinctrl-rzt2h.c

index 5927744c7a966c782b320126b9840daaa52751d4..4ba11a83b6047bb46cf71aaf43a17bb09e040936 100644 (file)
@@ -7,6 +7,7 @@
  * Copyright (C) 2025 Renesas Electronics Corporation.
  */
 
+#include <linux/array_size.h>
 #include <linux/bitfield.h>
 #include <linux/bitops.h>
 #include <linux/bits.h>
@@ -43,6 +44,7 @@
 #define PMC(m)         (0x400 + (m))
 #define PFC(m)         (0x600 + 8 * (m))
 #define PIN(m)         (0x800 + (m))
+#define DRCTL(n)       (0xa00 + 8 * (n))
 #define RSELP(m)       (0xc00 + (m))
 
 #define PM_MASK                        GENMASK(1, 0)
 #define PFC_PIN_MASK(pin)      (PFC_MASK << ((pin) * 8))
 #define PFC_FUNC_INTERRUPT     0
 
+#define DRCTL_DRV_PIN_MASK(pin)        (GENMASK_ULL(1, 0) << ((pin) * 8))
+#define DRCTL_PUD_PIN_MASK(pin)        (GENMASK_ULL(3, 2) << ((pin) * 8))
+#define DRCTL_SMT_PIN_MASK(pin)        (BIT_ULL(4) << ((pin) * 8))
+#define DRCTL_SR_PIN_MASK(pin) (BIT_ULL(5) << ((pin) * 8))
+
+#define DRCTL_PUD_NONE         0
+#define DRCTL_PUD_PULL_UP      1
+#define DRCTL_PUD_PULL_DOWN    2
+
 /*
  * Use 16 lower bits [15:0] for pin identifier
  * Use 8 higher bits [23:16] for pin mux function
@@ -91,6 +102,8 @@ struct rzt2h_pinctrl {
        atomic_t                        wakeup_path;
 };
 
+static const unsigned int rzt2h_drive_strength_ua[] = { 2500, 5000, 9000, 11800 };
+
 #define RZT2H_GET_BASE(pctrl, port) \
        ((port) > RZT2H_MAX_SAFETY_PORTS ? (pctrl)->base0 : (pctrl)->base1)
 
@@ -110,6 +123,37 @@ RZT2H_PINCTRL_REG_ACCESS(b, u8)
 RZT2H_PINCTRL_REG_ACCESS(w, u16)
 RZT2H_PINCTRL_REG_ACCESS(q, u64)
 
+static int rzt2h_drive_strength_ua_to_idx(unsigned int ua)
+{
+       unsigned int i;
+
+       for (i = 0; i < ARRAY_SIZE(rzt2h_drive_strength_ua); i++) {
+               if (rzt2h_drive_strength_ua[i] == ua)
+                       return i;
+       }
+
+       return -EINVAL;
+}
+
+static int rzt2h_drive_strength_idx_to_ua(unsigned int idx)
+{
+       if (idx >= ARRAY_SIZE(rzt2h_drive_strength_ua))
+               return -EINVAL;
+
+       return rzt2h_drive_strength_ua[idx];
+}
+
+static void rzt2h_pinctrl_drctl_rmwq(struct rzt2h_pinctrl *pctrl,
+                                    u32 port, u64 mask, u64 val)
+{
+       u32 offset = DRCTL(port);
+       u64 drctl;
+
+       guard(raw_spinlock_irqsave)(&pctrl->lock);
+       drctl = rzt2h_pinctrl_readq(pctrl, port, offset) & ~mask;
+       rzt2h_pinctrl_writeq(pctrl, port, drctl | val, offset);
+}
+
 static int rzt2h_validate_pin(struct rzt2h_pinctrl *pctrl, unsigned int offset)
 {
        u8 port = RZT2H_PIN_ID_TO_PORT(offset);
@@ -443,6 +487,210 @@ done:
        return ret;
 }
 
+static int rzt2h_pinctrl_pinconf_get(struct pinctrl_dev *pctldev,
+                                    unsigned int pin,
+                                    unsigned long *config)
+{
+       struct rzt2h_pinctrl *pctrl = pinctrl_dev_get_drvdata(pctldev);
+       u32 port, param = pinconf_to_config_param(*config);
+       unsigned int arg;
+       u8 port_pin;
+       u64 drctl;
+       int ret;
+
+       ret = rzt2h_validate_pin(pctrl, pin);
+       if (ret)
+               return ret;
+
+       port = RZT2H_PIN_ID_TO_PORT(pin);
+       port_pin = RZT2H_PIN_ID_TO_PIN(pin);
+
+       switch (param) {
+       case PIN_CONFIG_SLEW_RATE:
+               drctl = rzt2h_pinctrl_readq(pctrl, port, DRCTL(port));
+               arg = field_get(DRCTL_SR_PIN_MASK(port_pin), drctl);
+               break;
+
+       case PIN_CONFIG_BIAS_DISABLE:
+       case PIN_CONFIG_BIAS_PULL_UP:
+       case PIN_CONFIG_BIAS_PULL_DOWN:
+               drctl = rzt2h_pinctrl_readq(pctrl, port, DRCTL(port));
+               arg = field_get(DRCTL_PUD_PIN_MASK(port_pin), drctl);
+               /* for PIN_CONFIG_BIAS_PULL_UP/DOWN when enabled we just return 1 */
+               switch (arg) {
+               case DRCTL_PUD_NONE:
+                       if (param != PIN_CONFIG_BIAS_DISABLE)
+                               return -EINVAL;
+                       break;
+               case DRCTL_PUD_PULL_UP:
+                       if (param != PIN_CONFIG_BIAS_PULL_UP)
+                               return -EINVAL;
+                       arg = 1;
+                       break;
+               case DRCTL_PUD_PULL_DOWN:
+                       if (param != PIN_CONFIG_BIAS_PULL_DOWN)
+                               return -EINVAL;
+                       arg = 1;
+                       break;
+               default:
+                       return -EINVAL;
+               }
+               break;
+
+       case PIN_CONFIG_INPUT_SCHMITT_ENABLE:
+               drctl = rzt2h_pinctrl_readq(pctrl, port, DRCTL(port));
+               arg = field_get(DRCTL_SMT_PIN_MASK(port_pin), drctl);
+               if (!arg)
+                       return -EINVAL;
+               break;
+
+       case PIN_CONFIG_DRIVE_STRENGTH_UA: {
+               int idx_drv;
+
+               drctl = rzt2h_pinctrl_readq(pctrl, port, DRCTL(port));
+               arg = field_get(DRCTL_DRV_PIN_MASK(port_pin), drctl);
+               idx_drv = rzt2h_drive_strength_idx_to_ua(arg);
+               if (idx_drv < 0)
+                       return idx_drv;
+               arg = idx_drv;
+               break;
+       }
+
+       default:
+               return -ENOTSUPP;
+       }
+
+       *config = pinconf_to_config_packed(param, arg);
+       return 0;
+}
+
+static int rzt2h_pinctrl_pinconf_set(struct pinctrl_dev *pctldev,
+                                    unsigned int pin,
+                                    unsigned long *configs,
+                                    unsigned int num_configs)
+{
+       struct rzt2h_pinctrl *pctrl = pinctrl_dev_get_drvdata(pctldev);
+       unsigned int i;
+       u8 port_pin;
+       int ret;
+
+       ret = rzt2h_validate_pin(pctrl, pin);
+       if (ret)
+               return ret;
+
+       port_pin = RZT2H_PIN_ID_TO_PIN(pin);
+
+       for (i = 0; i < num_configs; i++) {
+               u32 arg = pinconf_to_config_argument(configs[i]);
+               u32 param = pinconf_to_config_param(configs[i]);
+               u64 mask, val;
+
+               switch (param) {
+               case PIN_CONFIG_SLEW_RATE:
+                       mask = DRCTL_SR_PIN_MASK(port_pin);
+                       val = field_prep(mask, !!arg);
+                       break;
+
+               case PIN_CONFIG_BIAS_DISABLE:
+               case PIN_CONFIG_BIAS_PULL_UP:
+               case PIN_CONFIG_BIAS_PULL_DOWN: {
+                       u32 bias;
+
+                       switch (param) {
+                       case PIN_CONFIG_BIAS_DISABLE:
+                               bias = DRCTL_PUD_NONE;
+                               break;
+                       case PIN_CONFIG_BIAS_PULL_UP:
+                               bias = DRCTL_PUD_PULL_UP;
+                               break;
+                       case PIN_CONFIG_BIAS_PULL_DOWN:
+                               bias = DRCTL_PUD_PULL_DOWN;
+                               break;
+                       }
+
+                       mask = DRCTL_PUD_PIN_MASK(port_pin);
+                       val = field_prep(mask, bias);
+                       break;
+               }
+
+               case PIN_CONFIG_INPUT_SCHMITT_ENABLE:
+                       mask = DRCTL_SMT_PIN_MASK(port_pin);
+                       val = field_prep(mask, !!arg);
+                       break;
+
+               case PIN_CONFIG_DRIVE_STRENGTH_UA: {
+                       int drv_idx;
+
+                       drv_idx = rzt2h_drive_strength_ua_to_idx(arg);
+                       if (drv_idx < 0)
+                               return drv_idx;
+
+                       mask = DRCTL_DRV_PIN_MASK(port_pin);
+                       val = field_prep(mask, drv_idx);
+                       break;
+               }
+
+               default:
+                       return -ENOTSUPP;
+               }
+
+               rzt2h_pinctrl_drctl_rmwq(pctrl, RZT2H_PIN_ID_TO_PORT(pin), mask, val);
+       }
+
+       return 0;
+}
+
+static int rzt2h_pinctrl_pinconf_group_get(struct pinctrl_dev *pctldev,
+                                          unsigned int group,
+                                          unsigned long *config)
+{
+       unsigned long prev_config = 0;
+       const unsigned int *pins;
+       unsigned int i, npins;
+       int ret;
+
+       ret = pinctrl_generic_get_group_pins(pctldev, group, &pins, &npins);
+       if (ret)
+               return ret;
+
+       for (i = 0; i < npins; i++) {
+               ret = rzt2h_pinctrl_pinconf_get(pctldev, pins[i], config);
+               if (ret)
+                       return ret;
+
+               /* Check config matches previous pins */
+               if (i && prev_config != *config)
+                       return -ENOTSUPP;
+
+               prev_config = *config;
+       }
+
+       return 0;
+}
+
+static int rzt2h_pinctrl_pinconf_group_set(struct pinctrl_dev *pctldev,
+                                          unsigned int group,
+                                          unsigned long *configs,
+                                          unsigned int num_configs)
+{
+       const unsigned int *pins;
+       unsigned int i, npins;
+       int ret;
+
+       ret = pinctrl_generic_get_group_pins(pctldev, group, &pins, &npins);
+       if (ret)
+               return ret;
+
+       for (i = 0; i < npins; i++) {
+               ret = rzt2h_pinctrl_pinconf_set(pctldev, pins[i], configs,
+                                               num_configs);
+               if (ret)
+                       return ret;
+       }
+
+       return 0;
+}
+
 static const struct pinctrl_ops rzt2h_pinctrl_pctlops = {
        .get_groups_count = pinctrl_generic_get_group_count,
        .get_group_name = pinctrl_generic_get_group_name,
@@ -459,6 +707,15 @@ static const struct pinmux_ops rzt2h_pinctrl_pmxops = {
        .strict = true,
 };
 
+static const struct pinconf_ops rzt2h_pinctrl_confops = {
+       .is_generic = true,
+       .pin_config_get = rzt2h_pinctrl_pinconf_get,
+       .pin_config_set = rzt2h_pinctrl_pinconf_set,
+       .pin_config_group_set = rzt2h_pinctrl_pinconf_group_set,
+       .pin_config_group_get = rzt2h_pinctrl_pinconf_group_get,
+       .pin_config_config_dbg_show = pinconf_generic_dump_config,
+};
+
 static int rzt2h_gpio_request(struct gpio_chip *chip, unsigned int offset)
 {
        struct rzt2h_pinctrl *pctrl = gpiochip_get_data(chip);
@@ -890,6 +1147,7 @@ static int rzt2h_pinctrl_register(struct rzt2h_pinctrl *pctrl)
        desc->npins = pctrl->data->n_port_pins;
        desc->pctlops = &rzt2h_pinctrl_pctlops;
        desc->pmxops = &rzt2h_pinctrl_pmxops;
+       desc->confops = &rzt2h_pinctrl_confops;
        desc->owner = THIS_MODULE;
 
        pins = devm_kcalloc(dev, desc->npins, sizeof(*pins), GFP_KERNEL);