Re: [PATCH v2 05/13] pinctrl: Add Microsemi Ocelot SoC driver

From: Linus Walleij
Date: Wed Dec 13 2017 - 03:15:28 EST


On Fri, Dec 8, 2017 at 4:46 PM, Alexandre Belloni
<alexandre.belloni@xxxxxxxxxxxxxxxxxx> wrote:

> The Microsemi Ocelot SoC has a few pins that can be used as GPIOs or take
> multiple other functions. Add a driver for the pinmuxing and the GPIOs.
>
> There is currently no support for interrupts.
>
> Cc: Linus Walleij <linus.walleij@xxxxxxxxxx>
> Cc: linux-gpio@xxxxxxxxxxxxxxx
> Signed-off-by: Alexandre Belloni <alexandre.belloni@xxxxxxxxxxxxxxxxxx>

This looks very good. Nice work!

I was close to just applying it but found some very minor things.
When you resend this just send this patch and I can apply it directly
since there are only Kconfig symbol dependencies and no
compile-time dependencies in this patch.

> +config PINCTRL_OCELOT
> + bool "Pinctrl driver for the Microsemi Ocelot SoCs"
> + default y
> + depends on OF
> + depends on MSCC_OCELOT || COMPILE_TEST
> + select GENERIC_PINCONF
> + select GENERIC_PINCTRL_GROUPS
> + select GENERIC_PINMUX_FUNCTIONS
> + select REGMAP_MMIO

select GPIOLIB

When you run COMPILE_TEST you don't know if you have
GPIOLIB so select it.

> +// SPDX-License-Identifier: (GPL-2.0 OR MIT)

Wow never saw that before. OK I guess.

> +#include <linux/compiler.h>
> +#include <linux/gpio.h>

Just:
#include <linux/gpio/driver.h>

<linux/gpio.h> is a legacy include and should not be used.

> +static int ocelot_pinmux_set_mux(struct pinctrl_dev *pctldev,
> + unsigned int selector, unsigned int group)
> +{
> + struct ocelot_pinctrl *info = pinctrl_dev_get_drvdata(pctldev);
> + struct ocelot_pin_caps *pin = ocelot_pins[group].drv_data;
> + int f;
> +
> + f = ocelot_pin_function_idx(group, selector);
> + if (f < 0)
> + return -EINVAL;
> +
> + regmap_update_bits(info->map, OCELOT_GPIO_ALT0, BIT(pin->pin),
> + f << pin->pin);
> + regmap_update_bits(info->map, OCELOT_GPIO_ALT1, BIT(pin->pin),
> + f << (pin->pin - 1));

You need to add some comment on what is happening here and how the
bits are used because just reading these two lines is pretty hard.

I guess f = 0, 1, 2 .... 31 or so.

pin->pin is also 0, 1, 2 ... 31?

BIT(pin->pin) is pretty self-evident. It is masking the bit controlling
this pin in each register.

But setting bits (f << (pin->pin)) and then in the other register
(f << (pin->pin -1))?

Maybe you should even add an illustrative dev_dbg() print here
showing which bits you mask and set, or use some helper bools
so it is crystal clear what is going on.

So there is two registers to select "alternative functions" (I guess?)
And each has one bit for the *same* pin.

This is the case also in drivers/pinctrl/nomadik/pinctrl-nomadik.c.
It turns out to be a pretty horrible design decision: since the two
bits are not changed in the same register transaction, switching
from say function "00" to function "11" creates a "glitch" where
you first activate funcion "10" after writing the first register,
then finally go to function "11" after writing the second.

This had horrible electrical consequences and required special
workarounds in Nomadik so be on the lookout for this type
of problem.

> +static int ocelot_gpio_set_direction(struct pinctrl_dev *pctldev,
> + struct pinctrl_gpio_range *range,
> + unsigned int pin, bool input)
> +{
> + struct ocelot_pinctrl *info = pinctrl_dev_get_drvdata(pctldev);
> +
> + regmap_update_bits(info->map, OCELOT_GPIO_OE, BIT(pin),
> + input ? BIT(pin) : 0);
> +
> + return 0;
> +}
(...)
> +static const struct pinmux_ops ocelot_pmx_ops = {
> + .get_functions_count = ocelot_get_functions_count,
> + .get_function_name = ocelot_get_function_name,
> + .get_function_groups = ocelot_get_function_groups,
> + .set_mux = ocelot_pinmux_set_mux,
> + .gpio_set_direction = ocelot_gpio_set_direction,
> + .gpio_request_enable = ocelot_gpio_request_enable,
> +};

This looks a bit weird since the same register is also written
by the gpiochip to set direction.

If you want to relay the direction setting entirely to the pin
control subsystem, then just have your callbacks in the
gpiochip like this:

static int ocelot_gpio_direction_input(struct gpio_chip *chip, unsigned offset)
{
return pinctrl_gpio_direction_input(chip->base + offset);
}

static int ocelot_gpio_direction_output(struct gpio_chip *chip, unsigned offset,
int value)
{
struct ocelot_pinctrl *info = gpiochip_get_data(chip);
unsigned int pin = BIT(offset);

if (value)
regmap_write(info->map, OCELOT_GPIO_OUT_SET, pin);
else
regmap_write(info->map, OCELOT_GPIO_OUT_CLR, pin);

return pinctrl_gpio_direction_output(chip->base + offset);
}

Then all direction setting will just be relayed to the pin control
side.

Shouldn't this call also set up the altfunction so you know
the pin is now set in GPIO mode? That is how some other
drivers do it at least. But maybe you prefer to do the
muxing "on the side" (using pinmux ops only, and explicitly
setting up the line as GPIO in e.g. the device tree)?

In that case I think you might not need this callback at all.

Also: are you should you do not need to disable OCELOT_GPIO_OE
in the .gpio_disable_free() callback?

> +static const struct pinctrl_ops ocelot_pctl_ops = {
> + .get_groups_count = ocelot_pctl_get_groups_count,
> + .get_group_name = ocelot_pctl_get_group_name,
> + .get_group_pins = ocelot_pctl_get_group_pins,
> + .dt_node_to_map = pinconf_generic_dt_node_to_map_pin,
> + .dt_free_map = pinconf_generic_dt_free_map,
> +};

Nice use of the generic parsers, thanks!

> + ret = devm_gpiochip_add_data(&pdev->dev, gc, info);
> + if (ret)
> + return ret;
> + //TODO irqchip

/* Please use oldschool comments for now, the license on the top is
fine though */

> +static const struct regmap_config ocelot_pinctrl_regmap_config = {
> + .reg_bits = 32,
> + .val_bits = 32,
> + .reg_stride = 4,
> +};

Looks like it could have some more limitations (like max register
and so on) but it's OK.

> + base = devm_ioremap_resource(dev,
> + platform_get_resource(pdev, IORESOURCE_MEM, 0));
> + if (IS_ERR(base)) {
> + dev_err(dev, "Failed to ioremap registers\n");
> + return PTR_ERR(base);
> + }
> +
> + info->map = devm_regmap_init_mmio(dev, base,
> + &ocelot_pinctrl_regmap_config);
> + if (IS_ERR(info->map)) {
> + dev_err(dev, "Failed to create regmap\n");
> + return PTR_ERR(info->map);
> + }

Nice use of regmap MMIO!

Yours,
Linus Walleij