pinctrl: uniphier: support drive-strength configuration
[oweals/u-boot.git] / drivers / pinctrl / uniphier / pinctrl-uniphier-core.c
index 916d0519110a95edf1aad55af3a76b0252926edf..a5935e84de3e15fa22bccd81a568da9d16e90468 100644 (file)
@@ -16,6 +16,9 @@
 
 #define UNIPHIER_PINCTRL_PINMUX_BASE   0x1000
 #define UNIPHIER_PINCTRL_LOAD_PINMUX   0x1700
+#define UNIPHIER_PINCTRL_DRVCTRL_BASE  0x1800
+#define UNIPHIER_PINCTRL_DRV2CTRL_BASE 0x1900
+#define UNIPHIER_PINCTRL_DRV3CTRL_BASE 0x1980
 #define UNIPHIER_PINCTRL_PUPDCTRL_BASE 0x1a00
 #define UNIPHIER_PINCTRL_IECTRL                0x1d00
 
@@ -141,10 +144,25 @@ static const struct pinconf_param uniphier_pinconf_params[] = {
        { "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 },
+       { "drive-strength", PIN_CONFIG_DRIVE_STRENGTH, 0 },
        { "input-enable", PIN_CONFIG_INPUT_ENABLE, 1 },
        { "input-disable", PIN_CONFIG_INPUT_ENABLE, 0 },
 };
 
+static const struct uniphier_pinctrl_pin *
+uniphier_pinctrl_pin_get(struct uniphier_pinctrl_priv *priv, unsigned int pin)
+{
+       const struct uniphier_pinctrl_pin *pins = priv->socdata->pins;
+       int pins_count = priv->socdata->pins_count;
+       int i;
+
+       for (i = 0; i < pins_count; i++)
+               if (pins[i].number == pin)
+                       return &pins[i];
+
+       return NULL;
+}
+
 static int uniphier_pinconf_bias_set(struct udevice *dev, unsigned int pin,
                                     unsigned int param, unsigned int arg)
 {
@@ -185,6 +203,86 @@ static int uniphier_pinconf_bias_set(struct udevice *dev, unsigned int pin,
        return 0;
 }
 
+static const unsigned int uniphier_pinconf_drv_strengths_1bit[] = {
+       4, 8,
+};
+
+static const unsigned int uniphier_pinconf_drv_strengths_2bit[] = {
+       8, 12, 16, 20,
+};
+
+static const unsigned int uniphier_pinconf_drv_strengths_3bit[] = {
+       4, 5, 7, 9, 11, 12, 14, 16,
+};
+
+static int uniphier_pinconf_drive_set(struct udevice *dev, unsigned int pin,
+                                     unsigned int strength)
+{
+       struct uniphier_pinctrl_priv *priv = dev_get_priv(dev);
+       const struct uniphier_pinctrl_pin *desc;
+       const unsigned int *strengths;
+       unsigned int base, stride, width, drvctrl, reg, shift;
+       u32 val, mask, tmp;
+
+       desc = uniphier_pinctrl_pin_get(priv, pin);
+       if (WARN_ON(!desc))
+               return -EINVAL;
+
+       switch (uniphier_pin_get_drv_type(desc->data)) {
+       case UNIPHIER_PIN_DRV_1BIT:
+               strengths = uniphier_pinconf_drv_strengths_1bit;
+               base = UNIPHIER_PINCTRL_DRVCTRL_BASE;
+               stride = 1;
+               width = 1;
+               break;
+       case UNIPHIER_PIN_DRV_2BIT:
+               strengths = uniphier_pinconf_drv_strengths_2bit;
+               base = UNIPHIER_PINCTRL_DRV2CTRL_BASE;
+               stride = 2;
+               width = 2;
+               break;
+       case UNIPHIER_PIN_DRV_3BIT:
+               strengths = uniphier_pinconf_drv_strengths_3bit;
+               base = UNIPHIER_PINCTRL_DRV3CTRL_BASE;
+               stride = 4;
+               width = 3;
+               break;
+       default:
+               /* drive strength control is not supported for this pin */
+               return -EINVAL;
+       }
+
+       drvctrl = uniphier_pin_get_drvctrl(desc->data);
+       drvctrl *= stride;
+
+       reg = base + drvctrl / 32 * 4;
+       shift = drvctrl % 32;
+       mask = (1U << width) - 1;
+
+       for (val = 0; val <= mask; val++) {
+               if (strengths[val] > strength)
+                       break;
+       }
+
+       if (val == 0) {
+               dev_err(dev, "unsupported drive strength %u mA for pin %s\n",
+                       strength, desc->name);
+               return -EINVAL;
+       }
+
+       if (!mask)
+               return 0;
+
+       val--;
+
+       tmp = readl(priv->base + reg);
+       tmp &= ~(mask << shift);
+       tmp |= (mask & val) << shift;
+       writel(tmp, priv->base + reg);
+
+       return 0;
+}
+
 static int uniphier_pinconf_set(struct udevice *dev, unsigned int pin,
                                unsigned int param, unsigned int arg)
 {
@@ -197,6 +295,9 @@ static int uniphier_pinconf_set(struct udevice *dev, unsigned int pin,
        case PIN_CONFIG_BIAS_PULL_PIN_DEFAULT:
                ret = uniphier_pinconf_bias_set(dev, pin, param, arg);
                break;
+       case PIN_CONFIG_DRIVE_STRENGTH:
+               ret = uniphier_pinconf_drive_set(dev, pin, arg);
+               break;
        case PIN_CONFIG_INPUT_ENABLE:
                ret = uniphier_pinconf_input_enable(dev, pin, arg);
                break;