Skip to content

drivers/pinctrl: Add pad read-back and a procfs entry - #19871

Open
Fishwaldo wants to merge 1 commit into
apache:masterfrom
Fishwaldo:upstream-pinctrl-procfs
Open

drivers/pinctrl: Add pad read-back and a procfs entry#19871
Fishwaldo wants to merge 1 commit into
apache:masterfrom
Fishwaldo:upstream-pinctrl-procfs

Conversation

@Fishwaldo

@Fishwaldo Fishwaldo commented Aug 16, 2026

Copy link
Copy Markdown
Contributor

Summary

The pinctrl interface is write only: every operation sets a property, and
nothing reports what a pin currently holds. During board bring up the useful
question is the opposite one, and the interface meant to describe pins cannot
answer it.

This adds an optional get_pad method that fills a struct pinctrl_padinfo_s
describing one pad, and two callers for it:

  • /proc/pinctrl, under the new CONFIG_PINCTRL_PROCFS, printing one
    key:value line per pad. Every line carries the same tokens in the same
    order, with - for a field the pad does not have, so the file can be parsed.
    The framework owns the format; controllers only supply data.
  • a PINCTRLC_GETPAD ioctl, giving userspace the programmatic read-back that
    text cannot serve, e.g. reading a pad back after setting it. The structure
    embeds its strings rather than pointing at them, so one shape serves both
    callers across the user/kernel boundary.

Everything here is optional. A controller that does not implement get_pad
is still listed, and the ioctl returns -ENOTSUP. A pad need not have every
field: fill what it has and set the matching PINCTRL_HAVE_* bit, and anything
the structure has no member for goes in extra as further key:value text.
Pad and function names are entirely optional too — leave them empty and pads
are reported by number alone. PINCTRL_PADNAME() and two lookup helpers are
provided for controllers that do want names, so each one need not invent its own
table.

A minimal controller is about this much:

/* Optional: one entry per pad, its name then the name of each function
 * select in select order.  NULL marks a select the manual does not name.
 */

static const struct pinctrl_padname_s g_mychip_padnames[] =
{
  [MYCHIP_PAD_I2C0_SCL] = PINCTRL_PADNAME("I2C0_SCL", "I2C0_SCL",
                                          NULL, "GPIO44"),
  [MYCHIP_PAD_SPI0_CLK] = PINCTRL_PADNAME("SPI0_CLK", "SPI0_CLK"),
  [MYCHIP_PAD_XIN]      = PINCTRL_PADNAME("XIN", NULL),
};

static int mychip_getpad(struct pinctrl_dev_s *dev, uint32_t pin,
                         struct pinctrl_padinfo_s *info)
{
  uint32_t val = getreg32(MYCHIP_PAD(pin));
  FAR const char *name;

  info->have     = PINCTRL_HAVE_FUNCTION | PINCTRL_HAVE_PULL;
  info->function = (val & PAD_FUNC_MASK) >> PAD_FUNC_SHIFT;
  info->pullup   = (val & PAD_PU) != 0;
  info->pulldown = (val & PAD_PD) != 0;

  /* Names are optional; both helpers return NULL when a name is absent,
   * which leaves the strings empty.
   */

  name = pinctrl_padname(g_mychip_padnames, nitems(g_mychip_padnames),
                         pin);
  if (name != NULL)
    {
      strlcpy(info->name, name, sizeof(info->name));
    }

  name = pinctrl_funcname(g_mychip_padnames, nitems(g_mychip_padnames),
                          pin, info->function);
  if (name != NULL)
    {
      strlcpy(info->funcname, name, sizeof(info->funcname));
    }

  /* Anything the structure has no member for */

  snprintf(info->extra, sizeof(info->extra), "ms:%u", (val >> 8) & 3);
  return OK;
}

static const struct pinctrl_ops_s g_mychip_ops =
{
  ...
  .get_pad = mychip_getpad,
};

The documentation is updated with the method, the validity bits, the optional
naming and the procfs entry.

Impact

New feature, off by default. CONFIG_PINCTRL_PROCFS depends on
FS_PROCFS_REGISTER; with it unset nothing here is compiled.

struct pinctrl_ops_s gains one optional member and struct pinctrl_dev_s
gains npins. There are currently no pinctrl providers in master, so no
existing driver needs updating. The first consumer will be the ESWIN EIC7700X
pad multiplexer, in a pull request following shortly; it drives 166 pads through
four different field layouts, which is what the have bits and extra exist
for.

No change to the existing five operations, their ioctls or their behaviour.

Worth flagging to reviewers: no in-tree configuration enables CONFIG_PINCTRL
at all
, so CI will not compile this code. A green run says nothing about it,
and the testing below is the only evidence.

Testing

Host: macOS 26.5.1 (arm64), riscv-none-elf-gcc 15.2.0, Sphinx 6.2.1.

Build, since CI cannot: sim:nsh with PINCTRL, FS_PROCFS,
FS_PROCFS_REGISTER and PINCTRL_PROCFS forced on compiles
drivers/pinctrl/pinctrl.c clean; also built with PINCTRL_PROCFS off to check
the disabled path. Documentation builds with no new warnings.

Hardware: ESWIN EIC7700 EVB (EIC7700X, 4 x RV64GC), booted over TFTP with an
out-of-tree pinctrl driver implementing get_pad for all 166 pads.

Board start up:

[CPU0] clk: registered 264 clocks, 0 failed
[CPU0] pinctrl: 166 pads, 3 configured
nsh> cat /proc/pinctrl
pinctrl0: 166 pads
0    CHIP_MODE            func:0 sel:CHIP_MODE        ds:0 pu:0 pd:1 ie:1 smt:1 slew:-
5    XIN                  func:- sel:-                ds:12 pu:- pd:- ie:- smt:- slew:- frs:2 rd:0
91   I2C0_SCL             func:0 sel:I2C0_SCL         ds:1 pu:0 pd:0 ie:1 smt:0 slew:-
141  S_MODE               func:2 sel:GPIO94           ds:1 pu:0 pd:0 ie:1 smt:0 slew:-
163  LPDDR_REF_CLK        func:- sel:-                ds:0 pu:0 pd:0 ie:1 smt:0 slew:- ms:3
164  ADDR_RGMII0_SEL_MODE func:- sel:-                ds:- pu:- pd:- ie:- smt:- slew:- ms1:1 ms2:1

That exercises all four of the chip's pad layouts: pad 0 a general pad, 5 an
oscillator pad with no function select and its own frs/rd fields, 163 an
RGMII pad carrying ms, and 164 a mode-select pad that has almost nothing and
renders as - throughout. sel:GPIO94 on pad 141 is a named function select.

Checked on that output:

  • all 166 pads present, strictly sequential, no gap or repeat at any read
    boundary
    — the file is far larger than one read buffer, so this exercises the
    pos accounting across many read() calls
  • every line carries the identical token set; an awk pass splitting on the
    fields finds no line that deviates
  • no trailing whitespace, and no truncated record

A controller without get_pad was checked by removing the method: the
controller is still listed with a note, and the ioctl returns -ENOTSUP.

@github-actions github-actions Bot added Area: Drivers Drivers issues Size: L The size of the change in this PR is large labels Aug 16, 2026
@github-actions

github-actions Bot commented Aug 16, 2026

Copy link
Copy Markdown

MemBrowse Memory Report

No memory changes detected for:

Comment thread include/nuttx/pinctrl/pinctrl.h Outdated
Comment thread include/nuttx/pinctrl/pinctrl.h Outdated
Comment thread drivers/pinctrl/Kconfig
@Fishwaldo
Fishwaldo force-pushed the upstream-pinctrl-procfs branch from 652fef8 to c4bf411 Compare August 18, 2026 04:33
The pinctrl interface is write only: every operation sets a property,
and nothing reports what a pin currently holds.

Adds an optional get_pad method describing one pad as a structure: the
settable fields, each with a validity bit because a pad need not have
them all, and a text member for the controller specific fields the
structure does not cover.  The structure embeds its strings rather than
pointing at them, so the same shape serves both callers.

The first caller is /proc/pinctrl, which renders one key:value line per
pad, every line the same tokens in the same order with - for a field the
pad does not have, so the file is machine parseable.  The framework owns
the format; controllers only supply data.

The second is a new PINCTRLC_GETPAD ioctl, which gives userspace the
read-back that text cannot: reading a pad back after setting it.

PINCTRL_PADNAME() and two lookup helpers let a controller declare its
pad and function-select names in one table instead of inventing its own.

Registration keeps a list, which the renderer iterates; pinctrl_dev_s
gains the pad count.  /proc/pinctrl is claimed when the first controller
appears; procfs_register() requires that procfs is not yet mounted,
which holds because controllers register during board or architecture
start up.

Documents the method, the validity bits and the optional naming, and
records that /proc/pinctrl exists.

Off by default and costs nothing when off.  No in-tree configuration
enables PINCTRL, so this builds only when a board turns it on.

Assisted-by: Claude:claude-opus-5
Signed-off-by: Justin Hammond <justin@dynam.ac>
@Fishwaldo
Fishwaldo force-pushed the upstream-pinctrl-procfs branch from c4bf411 to 5368fed Compare August 18, 2026 04:44
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Area: Drivers Drivers issues Size: L The size of the change in this PR is large

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants