Re: [PATCH 2/2] pinctrl: Introduce TI IOdelay configuration driver

From: Nishanth Menon
Date: Wed Mar 11 2015 - 08:39:17 EST


On Tue, Mar 10, 2015 at 6:03 AM, Linus Walleij <linus.walleij@xxxxxxxxxx> wrote:
> On Wed, Mar 4, 2015 at 1:00 AM, Nishanth Menon <nm@xxxxxx> wrote:
>
>> SoC family such as DRA7 family of processors have, in addition
>> to the regular muxing of pins (as done by pinctrl-single), an
>> additional hardware module called IODelay which is also expected to be
>> configured. This "IODelay" module has it's own register space that is
>> independent of the control module.
>>
>> It is advocated strongly in TI's official documentation considering
>> the existing design of the DRA7 family of processors during mux or
>> IODelay reconfiguration, there is a potential for a significant glitch
>> which may cause functional impairment to certain hardware. It is
>> hence recommended to do as little of muxing as absolutely necessary
>> without I/O isolation (which can only be done in initial stages of
>> bootloader).
>>
>> NOTE: with the system wide I/O isolation scheme present in DRA7 SoC
>> family, it is not reasonable to do stop all I/O operations for every
>> such pad configuration scheme. So, we will let it glitch when used in
>> this mode.
>>
>> Even with the above limitation, certain functionality such as MMC has
>> mandatory need for IODelay reconfiguration requirements, depending on
>> speed of transfer. In these cases, with careful examination of usecase
>> involved, the expected glitch can be controlled such that it does not
>> impact functionality.
>>
>> In short, IODelay module support as a padconf driver being introduced
>> here is not expected to do SoC wide I/O Isolation and is meant for
>> a limited subset of IODelay configuration requirements that need to
>> be dynamic and whose glitchy behavior will not cause functionality
>> failure for that interface.
>>
>> Signed-off-by: Nishanth Menon <nm@xxxxxx>
>> Signed-off-by: Lokesh Vutla <lokeshvutla@xxxxxx>
>
> Pending the conclusion of my remark on the bindings that this
> should use the generic pin config bindings... here are some
> "normal" review comments.
>
>> +config PINCTRL_TI_IODELAY
>> + bool "TI IODelay Module pinconf driver"
>> + depends on OF
>> + select PINCONF
>> + select GENERIC_PINCONF
>> + select REGMAP_MMIO
>> + help
>> + Say Y here to support Texas Instruments' IODelay pinconf driver.
>> + IODelay module is used for the DRA7 SoC family. This driver is in
>> + addition to PINCTRL_SINGLE which controls the mux.
>
> If the driver is "in addition to PINCTRL_SINGLE", then it
> should depend on it.
>
>> +/* Should I change this? Abuse? */
>> +#define IODELAY_MUX_PINS_NAME "pinctrl-single,pins"
>
> Yeah this is abuse and is why you should use the generic
> bindings with just "pins" instead, and use the generic pinconf
> parameters to set them up.
>
>> + * struct ti_iodelay_conf_vals - Description of each configuration parameters.
>> + * @offset: Configuration register offset
>> + * @a_delay: Agnostic Delay (in ps)
>> + * @g_delay: Gnostic Delay (in ps)
>> + */
>> +struct ti_iodelay_conf_vals {
>> + u16 offset;
>> + u16 a_delay;
>> + u16 g_delay;
>> +};
>
> So I don't think it's a good idea to get the register offset from
> the device tree. The driver should know these.
>
>> +/**
>> + * struct ti_iodelay_reg_values - Computed io_reg configuration values (see TRM)
>> + * @coarse_ref_count: Coarse reference count
>> + * @coarse_delay_count: Coarse delay count
>> + * @fine_ref_count: Fine reference count
>> + * @fine_delay_count: Fine Delay count
>> + * @ref_clk_period: Reference Clock period
>> + * @cdpe: Coarse delay parameter
>> + * @fdpe: Fine delay parameter
>> + */
>> +struct ti_iodelay_reg_values {
>> + u16 coarse_ref_count;
>
> If you're doing reference counting, use kref.
> <linux/kref.h>
>
>> + u16 coarse_delay_count;
>> +
>> + u16 fine_ref_count;
>
> Dito.
>
>> + u16 fine_delay_count;
>> +
>> + u16 ref_clk_period;
>> +
>> + u32 cdpe;
>> + u32 fdpe;
>> +};
>
>
>> +/**
>> + * struct ti_iodelay_pin_name - name of the pins
>> + * @name: name
>> + */
>> +struct ti_iodelay_pin_name {
>> + char name[IODELAY_REG_NAME_LEN];
>> +};
>
> What is this? The pin control subsystem already has a struct
> for naming pins. struct pinctrl_pin_desc.
>
> Are you reimplementing core functionality?
>
> The generic pin config reference pins by name, and since you
> obviously like to encode these into you data you can just
> as well use that method for this too.
>
> Again I think this is a side effect from using the pinctrl-single
> concepts, if you think of this as some other pinctrl driver
> I think this will fall out nicely.
>
>> +/**
>> + * struct ti_iodelay_device - Represents information for a IOdelay instance
>> + * @dev: device pointer
>> + * @reg_base: Remapped virtual address
>> + * @regmap: Regmap for this IOdelay instance
>> + * @pctl: Pinctrl device
>> + * @desc: pinctrl descriptor for pctl
>> + * @pa: pinctrl pin wise description
>> + * @names: names of the pins
>> + * @groups: list of pinconf groups for iodelay instance
>> + * @ngroups: number of groups in the list
>> + * @mutex: mutex to protect group list modification
>> + * @reg_data: Register definition data for the IODelay instance
>> + * @reg_init_conf_values: Initial configuration values.
>> + */
>> +struct ti_iodelay_device {
>> + struct device *dev;
>> + void __iomem *reg_base;
>> + struct regmap *regmap;
>> +
>> + struct pinctrl_dev *pctl;
>> + struct pinctrl_desc desc;
>> + struct pinctrl_pin_desc *pa;
>> + struct ti_iodelay_pin_name *names;
>
> Unhappy about that. pinctrl_desc contains
> struct pinctrl_pin_desc const *pins; !
>
>> +
>> + struct list_head groups;
>> + int ngroups;
>> + struct mutex mutex; /* list protection */
>
> Why is this needed? Usually the pin control core
> serialize calls into the drivers. Care to explain? Have
> the potential race really triggered?
>
>> +static inline u32 ti_iodelay_compute_dpe(u16 period, u16 ref, u16 delay,
>> + u16 delay_m)
>> +{
>> + u64 m, d;
>> +
>> + /* Handle overflow conditions */
>> + m = 10 * (u64)period * (u64)ref;
>> + d = 2 * (u64)delay * (u64)delay_m;
>> +
>> + /* Truncate result back to 32 bits */
>> + return div64_u64(m, d);
>> +}
>
> Serious business ...
>
>> +/**
>> + * ti_iodelay_pinconf_set() - Configure the pin configuration
>> + * @iod: IODelay device
>> + * @val: Configuration value
>> + *
>> + * Update the configuration register as per TRM and lockup once done.
>> + * *IMPORTANT NOTE* SoC TRM does recommend doing iodelay programmation only
>> + * while in Isolation. But, then, isolation also implies that every pin
>> + * on the SoC(including DDR) will be isolated out. The only benefit being
>> + * a glitchless configuration, However, the intent of this driver is purely
>> + * to support a "glitchy" configuration where applicable.
>> + *
>> + * Return: 0 in case of success, else appropriate error value
>> + */
>> +static int ti_iodelay_pinconf_set(struct ti_iodelay_device *iod,
>> + struct ti_iodelay_conf_vals *val)
>> +{
>> + const struct ti_iodelay_reg_data *reg = iod->reg_data;
>> + struct ti_iodelay_reg_values *ival = &iod->reg_init_conf_values;
>> + struct device *dev = iod->dev;
>> + u32 g_delay_coarse, g_delay_fine;
>> + u32 a_delay_coarse, a_delay_fine;
>> + u32 c_elements, f_elements;
>> + u32 total_delay;
>> + u32 reg_mask, reg_val, tmp_val;
>> + int r;
>> +
>> + /* NOTE: Truncation is expected in all division below */
>> + g_delay_coarse = val->g_delay / 920;
>> + g_delay_fine = ((val->g_delay % 920) * 10) / 60;
>> +
>> + a_delay_coarse = val->a_delay / ival->cdpe;
>> + a_delay_fine = ((val->a_delay % ival->cdpe) * 10) / ival->fdpe;
>> +
>> + c_elements = g_delay_coarse + a_delay_coarse;
>> + f_elements = (g_delay_fine + a_delay_fine) / 10;
>> +
>> + if (f_elements > 22) {
>> + total_delay = c_elements * ival->cdpe + f_elements * ival->fdpe;
>> + c_elements = total_delay / ival->cdpe;
>> + f_elements = (total_delay % ival->cdpe) / ival->fdpe;
>> + }
>> +
>> + reg_mask = reg->signature_mask;
>> + reg_val = reg->signature_value << __ffs(reg->signature_mask);
>> +
>> + reg_mask |= reg->binary_data_coarse_mask;
>> + tmp_val = c_elements << __ffs(reg->binary_data_coarse_mask);
>> + if (tmp_val & ~reg->binary_data_coarse_mask) {
>> + dev_err(dev, "Masking overflow of coarse elements %08x\n",
>> + tmp_val);
>> + tmp_val &= reg->binary_data_coarse_mask;
>> + }
>> + reg_val |= tmp_val;
>> +
>> + reg_mask |= reg->binary_data_fine_mask;
>> + tmp_val = f_elements << __ffs(reg->binary_data_fine_mask);
>> + if (tmp_val & ~reg->binary_data_fine_mask) {
>> + dev_err(dev, "Masking overflow of fine elements %08x\n",
>> + tmp_val);
>> + tmp_val &= reg->binary_data_fine_mask;
>> + }
>> + reg_val |= tmp_val;
>> +
>> + /* Write with lock value - we DONOT want h/w updates */
>> + reg_mask |= reg->lock_mask;
>> + reg_val |= reg->lock_val << __ffs(reg->lock_mask);
>> + r = regmap_update_bits(iod->regmap, val->offset, reg_mask, reg_val);
>> +
>> + dev_dbg(dev, "Set reg 0x%x Delay(a=%d g=%d), Elements(C=%d F=%d)0x%x\n",
>> + val->offset, val->a_delay, val->g_delay, c_elements,
>> + f_elements, reg_val);
>> +
>> + return r;
>> +}
>
> Looking at this terse thing I think it's obviously easier to use
> a more verbose device tree description with
>
> drive-delay = <some SI unit>;
>
>
>> + /* unlock the IOdelay region */
>> + r = regmap_update_bits(iod->regmap, reg->reg_global_lock_offset,
>> + reg->global_lock_mask, reg->global_unlock_val);
>> + if (r)
>> + return r;
>> +
>> + /* Read up Recalibration sequence done by bootloader */
>> + r = regmap_read(iod->regmap, reg->reg_refclk_offset, &val);
>> + if (r)
>> + return r;
>> + ival->ref_clk_period = ti_iodelay_extract(val, reg->refclk_period_mask);
>> + dev_dbg(dev, "refclk_period=0x%04x\n", ival->ref_clk_period);
>
> That sounds hightec. So the bootloader calibrates the delays
> and computes the refclock period? So is that sime PLL or something?
>
>> + r = regmap_read(iod->regmap, reg->reg_coarse_offset, &val);
>> + if (r)
>> + return r;
>> + ival->coarse_ref_count =
>> + ti_iodelay_extract(val, reg->coarse_ref_count_mask);
>> + ival->coarse_delay_count =
>> + ti_iodelay_extract(val, reg->coarse_delay_count_mask);
>> + if (!ival->coarse_delay_count) {
>> + dev_err(dev, "Invalid Coarse delay count (0) (reg=0x%08x)\n",
>> + val);
>> + return -EINVAL;
>> + }
>> + ival->cdpe = ti_iodelay_compute_dpe(ival->ref_clk_period,
>> + ival->coarse_ref_count,
>> + ival->coarse_delay_count, 88);
>
> 88? What does that mean... This should be #defined I think.
>
>> +static int ti_iodelay_dt_node_to_map(struct pinctrl_dev *pctldev,
>> + struct device_node *np,
>> + struct pinctrl_map **map,
>> + unsigned *num_maps)
>
> If you use the generic bindings this quite complex parsing and
> everything associated with it goes away into the core.
>
>> +static void ti_iodelay_dt_free_map(struct pinctrl_dev *pctldev,
>> + struct pinctrl_map *map, unsigned num_maps)
>
> Dito.
>
> BTW even if you proceed this way I suspect these are verbatim
> copies from pinctrl-single so they shold obviouslyt be abstracted
> out as library functions in that case.
>
>> +/**
>> + * ti_iodelay_pinctrl_get_group_pins() - get group pins
>> + * @pctldev: pinctrl device representing IODelay device
>> + * @gselector: Group selector
>> + * @pins: pointer to the pins
>> + * @npins: number of pins
>> + *
>> + * Dummy implementation since we do not track pins, we track configurations
>> + * Forced by pinctrl's pinctrl_check_ops()
>> + *
>> + * Return: -EINVAL
>> + */
>> +static int ti_iodelay_pinctrl_get_group_pins(struct pinctrl_dev *pctldev,
>> + unsigned gselector,
>> + const unsigned **pins,
>> + unsigned *npins)
>> +{
>> + /* Dummy implementation - we dont do pin mux */
>> + return -EINVAL;
>> +}
>
> This is not about muxing. It is about enumerating the pins in the
> group that will be affected by the setting, which is something you
> will want to do when using the generic bindings.
>
>> + group = ti_iodelay_get_group(iod, gselector);
>
> And this seems to be your re-implementation of exactly that pin control
> core functionality. Which again should be shared with pinctrl-single if
> you anyway go down this path.
>
> I'll halt review here pending discussion on the bindings & stuff.

I assume we want to close on binding before looking at implementation
- definitely needs a rewrite - only guidance I'd probably need is
about which model you'd suggest I should follow(pinconf/pinctrl) once
we are done with the bindings discussion.

--
---
Regards,
Nishanth Menon
--
To unsubscribe from this list: send the line "unsubscribe linux-kernel" in
the body of a message to majordomo@xxxxxxxxxxxxxxx
More majordomo info at http://vger.kernel.org/majordomo-info.html
Please read the FAQ at http://www.tux.org/lkml/