Linus, (and other people interested in pinmux!)
I've started to look at porting the Tegra pinmux driver to your pinctrl subsystem. In order to replace the existing pinmux driver, I need a way to support configuring options such as tri-state, pull-up/down, drive- strength, etc.
At this point, I have a couple of options:
1) Initialize all those parameters (and indeed the initial system-level mux configuration) using custom code in the Tegra pinmux driver, and just use the pin controller subsystem APIs for dynamic pin-muxing. As you recall, I did post some patches to the Tegra pinmux driver to do this, but I'm hoping to do (2) instead.
2) Enhance the pin controller subsystem to support configuring these properties.
The following is some issues that need to be thought about if we do (2):
Issue 1: Pinctrl core -> driver interface
I propose adding the following to pinctrl_ops:
int (*pin_config)(unsigned pin, u32 param, u32 data); int (*group_config)(unsigned group, u32 param, u32 data);
Where "param" is some driver-specific parameter, such as:
#define TEGRA_PINCTRL_PARAM_TRISTATE_ENABLE 0 #define TEGRA_PINCTRL_PARAM_PULL_TYPE 1 #define TEGRA_PINCTRL_PARAM_SCHMITT_ENABLE 2 #define TEGRA_PINCTRL_PARAM_HIGH_SPEED_ENABLE 3 #define TEGRA_PINCTRL_PARAM_DRIVE_STRENGTH 4 #define TEGRA_PINCTRL_PARAM_PULL_UP_STRENGTH 5 #define TEGRA_PINCTRL_PARAM_PULL_DOWN_STRENGTH 6 #define TEGRA_PINCTRL_PARAM_SLEW_RATE_RISING 7 #define TEGRA_PINCTRL_PARAM_SLEW_RATE_FALLING 8
Rationale:
Param being SoC-specific:
I looked at a bunch of existing pinmux drivers in the mainline kernel. There are certainly some common concepts between SoCs. However, the details are often different enough that I don't think attempting to unify the set of configuration options will be that successful; I doubt we could create a generic enough abstraction of these settings for a cross-SoC driver to be able to usefully name and select values for all the different pin/group options.
Data being a plain u32 rather than a pointer as your v7 patchset had it:
Using a pointer does allow passing arbitrary data into the driver, which seems useful. However, this also makes it much more difficult for the core pin controller API to handle the values, e.g. in the mapping table, in a device-tree representation thereof, etc.
Having both config_pin and config_group functions:
Tegra at least has settings that are applicable to groups. Most (all??) other SoCs apply configurations to individual pins. I think we need separate functions to make this explicit, so the function knows if it's looking up selector as a pin name (ID) or a group name (ID).
Issue 2: Do we need a new "pin settings" table passed from the board to the pin controller core?
Issue 3: Do we need to change "pin settings" on-the-fly, or only at boot?
These issues are closely related.
If we only care about statically configuring pins at boot time, then pinmux_register_mappings() could be enhanced to accept two tables; the existing one for the mux settings, and a second one that's essentially just a list of pin/group_config calls to make at pinmux init time.
However, if we ever want things to change dynamically (say, suspend/ resume), or only be activated when a device initializes (just like the mux settings), we need to enhance struct pinmux_map to contain either mux settings or config settings; perhaps rework it as follows:
enum pinmux_map_type { MUX, PIN_CONFIG, GROUP_CONFIG };
struct pinmux_map { const char *name; struct device *ctrl_dev; const char *ctrl_dev_name; enum pinmux_map_type type; union { struct { const char *function; const char *group; } mux; struct { u32 param; u32 data; } config; } data; struct device *dev; const char *dev_name; const bool hog_on_boot; };
Such that pinmux_enable would both activate all the mux settings, and also call pin/group_config based on the mapping table.
Issue 4: Device-tree
I'd like to enhance the pinmux core to allow the mapping table to be read from device tree. If we end up with a separate "pin configuration" table, the same applies there. Do you have any thoughts here, or should I just go ahead and create/propose something? I'd probably base this partially on my previous patches that do this within the Tegra pinmux driver itself.
Issue 5: Suspend/resume, and other state changes
First, some HW background on Tegra, which I imagine applies to any SoC capable of muxing a single "function" onto different sets of pins:
Lets take Tegra's first I2C controller. It can be mux'd onto pingroups "I2CP" or "RM". Given the register definitions (for each pingroup, there's a "function select" register field), it's possible to tell the HW to mux that I2C function onto *both* pingroups at once. However, we must avoid this, because this will cause incorrect HW operation; for input pins, both pingroups will be trying to drive the internal signal. At best, this will cause the I2C controller to receive garbled signals. At worst, it will cause damage to the chip. (I'm not sure which is true in Tegra's case).
For this reason, any time the pinmux driver is told to disable a function, it must program that pingroup's mux function to a "safe" value, which is guaranteed not to cause conflicts for any other possible mux settings on other pingroups. Possibly, the driver should tristate the pingroup too, to avoid whatever HW function the "safe" function is from actually driving any output pins and confusing the attached device. Our current driver doesn't tristate though.
Now let's suppose that we've enhanced the mapping table to support pin/ group_config values, and that a driver is written to expect a pinmux mapping table with the following mappings:
"active" "suspend"
Where the only difference between those two mappings is some pin/ group_config value. When switching between those two settings, the "active" mux values will be rolled back to some "safe" values, then the "suspend" mux values will be re-programmed. This may cause spurious changes in output signals, depending on what the "safe" function is exactly. It might even temporarily change some pins from inputs to outputs.
The pinmux API currently conflates two different things:
a) A device's ownership of certain pins.
b) The configuration of those pins.
I think we should aim for some mechanism of teasing those two concepts apart, and allowing dynamic transitioning between different configurations for the owned pins without completely releasing them.
There's also the slight issue that if you put() a mapping to change to another one, there's a small window where the driver doesn't own the pins, which might show up in a pinmux debug file, or allow another driver to take ownership of them.
One possibility might be to add another column to the mapping table, so that we end up with the search key being:
Search keys: device, name, configuration (new) Output data: list of mux settings, pin/group_config settings
This would modify the client driver API to something like:
Mapping table:
Driver mapping_name configuration settings ----------------------------------------- i2c - active group i2cp.mux = i2c i2c - active group i2cp.tristate = false i2c - suspend group i2cp.mux = i2c i2c - suspend group i2cp.tristate = true
Driver probe():
reservation = pinmux_reserve(device, NULL /* mapping_name */) pinmux_select(reservation, "active")
Driver suspend():
// Within this call, the pinmux driver doesn't have to program safe values // into HW, because it knows that any future programming on pins relevant // to the driver can only be driven by mapping table entries for the "4-bit" // reservation, and we assume the person creating those mapping table // entries didn't set up some problematic configuration // // The mapping tables may be set up so the only difference is some pin/ // group_config values here pinmux_select(reservation, "suspend")
Driver resume():
pinmux_select(reservation, "active")
Driver remove():
// Within this call, the pinmux driver programs the safe values into HW, // since the ownership of the pins is released; some other driver may // start using the pins or mux'd HW modules now. pinmux_unreserve(reservation)
In this scheme, the mapping table might even be enhanced to be a list of actions to perform on each transition, rather than completely describing the state of the mux in each state. That might optimize the set of actions performed by some state transitions, if the mapping table author desires.
What are your thoughts on this?
For reference, I read through all the pinmux drivers I could find in arch/ arm, and made a rough list of all the pin/group_config settings they support:
msm:
pads?:
drive strength = 2, 4, 6, 8, 10, 12, 14, 16mA pull = n, down, keeper, up
mxs:
pads:
drive strength = 4, 8, 12, 16mA voltage = 1.8, 3.3V pull = n, up
omap2 (2420, 2430, 34xx, 44xx):
pads:
off out enable off out value off pull enable off pull up wakeup enable
tegra20:
mux pingroups:
tri-state = y, n pull = n, up, down
drive pingroups:
high speed mode = y, n schmitt = y, n drive strength = /1, /2, /4, /8 pull down strength = 0..31 pull up strength = 0..31 slew rate rising = fastest, fast, slow, slowest slew rate falling = fastest, fast, slow, slowest
mxc v1:
??
mxc v3:
pads:
dvs? # drive strength? hys? # hysteresis? pke? # pull/keeper enable? pue? # pull enable (rather than keeper)? pull strength = 100k down, 47k up, 22k up ode? # open drain? slew rate = slow, fast
mxc mx1:
??
mxc mx3:
pads:
loopback pke? = none, enable (keeper?) pue? = (look at?) keeper (bit?), pud pull strength = 100k pull down, 100k pull up, 47k pull up, 22k pullup hysteresis = cmos, schmitt driver = cmos, open drain drive strength = normal, high, max slew rate = slow, fast
nomadik (ux500):
pads:
pull = n, up, down sleep mode = make input, no change or: sleep mode wakeup = enable, disable sleep mode pull = n, up, down sleep mode direction = in, out sleep mode value = 0, 1