Message ID | 1425427237-11511-3-git-send-email-nm@ti.com (mailing list archive) |
---|---|
State | New, archived |
Headers | show |
Nishanth Menon schreef op di 03-03-2015 om 18:00 [-0600]: > diff --git a/drivers/pinctrl/Kconfig b/drivers/pinctrl/Kconfig > index ee9f44ad7f02..8e463d75fbb2 100644 > --- a/drivers/pinctrl/Kconfig > +++ b/drivers/pinctrl/Kconfig > @@ -160,6 +160,17 @@ config PINCTRL_TEGRA_XUSB > select PINCONF > select PINMUX > > +config PINCTRL_TI_IODELAY > + bool "TI IODelay Module pinconf driver" This adds a boolean Kconfig symbol. > + 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. > + > config PINCTRL_TZ1090 > bool "Toumaz Xenif TZ1090 pin control driver" > depends on SOC_TZ1090 > diff --git a/drivers/pinctrl/Makefile b/drivers/pinctrl/Makefile > index 0475206dd600..e441cd71aedf 100644 > --- a/drivers/pinctrl/Makefile > +++ b/drivers/pinctrl/Makefile > @@ -28,6 +28,7 @@ obj-$(CONFIG_PINCTRL_TEGRA30) += pinctrl-tegra30.o > obj-$(CONFIG_PINCTRL_TEGRA114) += pinctrl-tegra114.o > obj-$(CONFIG_PINCTRL_TEGRA124) += pinctrl-tegra124.o > obj-$(CONFIG_PINCTRL_TEGRA_XUSB) += pinctrl-tegra-xusb.o > +obj-$(CONFIG_PINCTRL_TI_IODELAY) += pinctrl-ti-iodelay.o So this objectfile will either be built-in or not be built at all. > obj-$(CONFIG_PINCTRL_TZ1090) += pinctrl-tz1090.o > obj-$(CONFIG_PINCTRL_TZ1090_PDC) += pinctrl-tz1090-pdc.o > obj-$(CONFIG_PINCTRL_U300) += pinctrl-u300.o > diff --git a/drivers/pinctrl/pinctrl-ti-iodelay.c b/drivers/pinctrl/pinctrl-ti-iodelay.c > new file mode 100644 > index 000000000000..e4c6e25a781c > --- /dev/null > +++ b/drivers/pinctrl/pinctrl-ti-iodelay.c > @@ -0,0 +1,963 @@ >[...] > +#include <linux/err.h> > +#include <linux/init.h> > +#include <linux/io.h> > +#include <linux/list.h> > +#include <linux/module.h> This is, I think, a bit of a red flag. > +#include <linux/of_device.h> > +#include <linux/of.h> > +#include <linux/pinctrl/pinconf-generic.h> > +#include <linux/pinctrl/pinconf.h> > +#include <linux/pinctrl/pinctrl.h> > +#include <linux/regmap.h> > +#include <linux/slab.h> [...] > +static const struct of_device_id ti_iodelay_of_match[] = { > + {.compatible = "ti,dra7-iodelay", .data = &dra7_iodelay_data}, > + { /* Hopefully no more.. */ }, > +}; > +MODULE_DEVICE_TABLE(of, ti_iodelay_of_match); > + > +static struct platform_driver ti_iodelay_driver = { > + .probe = ti_iodelay_probe, > + .remove = ti_iodelay_remove, > + .driver = { > + .owner = THIS_MODULE, So THIS_MODULE will, basically, be NULL. > + .name = DRIVER_NAME, > + .of_match_table = ti_iodelay_of_match, > + }, > +}; > +module_platform_driver(ti_iodelay_driver); > + > +MODULE_AUTHOR("Texas Instruments, Inc."); > +MODULE_DESCRIPTION("Pinconf driver for TI's IO Delay module"); > +MODULE_LICENSE("GPL v2"); And these three macros will be preprocessed away. (Perhaps there are non-modular alternatives to MODULE_DEVICE_TABLE and module_platform_driver, but I'm not sure how those two macros actually work.) Paul Bolle -- To unsubscribe from this list: send the line "unsubscribe linux-omap" in the body of a message to majordomo@vger.kernel.org More majordomo info at http://vger.kernel.org/majordomo-info.html
* Paul Bolle <pebolle@tiscali.nl> [150304 14:58]: > Nishanth Menon schreef op di 03-03-2015 om 18:00 [-0600]: > > diff --git a/drivers/pinctrl/Kconfig b/drivers/pinctrl/Kconfig > > index ee9f44ad7f02..8e463d75fbb2 100644 > > --- a/drivers/pinctrl/Kconfig > > +++ b/drivers/pinctrl/Kconfig > > @@ -160,6 +160,17 @@ config PINCTRL_TEGRA_XUSB > > select PINCONF > > select PINMUX > > > > +config PINCTRL_TI_IODELAY > > + bool "TI IODelay Module pinconf driver" > > This adds a boolean Kconfig symbol. > > > + 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. > > + > > config PINCTRL_TZ1090 > > bool "Toumaz Xenif TZ1090 pin control driver" > > depends on SOC_TZ1090 > > diff --git a/drivers/pinctrl/Makefile b/drivers/pinctrl/Makefile > > index 0475206dd600..e441cd71aedf 100644 > > --- a/drivers/pinctrl/Makefile > > +++ b/drivers/pinctrl/Makefile > > @@ -28,6 +28,7 @@ obj-$(CONFIG_PINCTRL_TEGRA30) += pinctrl-tegra30.o > > obj-$(CONFIG_PINCTRL_TEGRA114) += pinctrl-tegra114.o > > obj-$(CONFIG_PINCTRL_TEGRA124) += pinctrl-tegra124.o > > obj-$(CONFIG_PINCTRL_TEGRA_XUSB) += pinctrl-tegra-xusb.o > > +obj-$(CONFIG_PINCTRL_TI_IODELAY) += pinctrl-ti-iodelay.o > > So this objectfile will either be built-in or not be built at all. > > > obj-$(CONFIG_PINCTRL_TZ1090) += pinctrl-tz1090.o > > obj-$(CONFIG_PINCTRL_TZ1090_PDC) += pinctrl-tz1090-pdc.o > > obj-$(CONFIG_PINCTRL_U300) += pinctrl-u300.o > > diff --git a/drivers/pinctrl/pinctrl-ti-iodelay.c b/drivers/pinctrl/pinctrl-ti-iodelay.c > > new file mode 100644 > > index 000000000000..e4c6e25a781c > > --- /dev/null > > +++ b/drivers/pinctrl/pinctrl-ti-iodelay.c > > @@ -0,0 +1,963 @@ > >[...] > > +#include <linux/err.h> > > +#include <linux/init.h> > > +#include <linux/io.h> > > +#include <linux/list.h> > > +#include <linux/module.h> > > This is, I think, a bit of a red flag. > > > +#include <linux/of_device.h> > > +#include <linux/of.h> > > +#include <linux/pinctrl/pinconf-generic.h> > > +#include <linux/pinctrl/pinconf.h> > > +#include <linux/pinctrl/pinctrl.h> > > +#include <linux/regmap.h> > > +#include <linux/slab.h> > > [...] > > > +static const struct of_device_id ti_iodelay_of_match[] = { > > + {.compatible = "ti,dra7-iodelay", .data = &dra7_iodelay_data}, > > + { /* Hopefully no more.. */ }, > > +}; > > +MODULE_DEVICE_TABLE(of, ti_iodelay_of_match); > > + > > +static struct platform_driver ti_iodelay_driver = { > > + .probe = ti_iodelay_probe, > > + .remove = ti_iodelay_remove, > > + .driver = { > > + .owner = THIS_MODULE, > > So THIS_MODULE will, basically, be NULL. > > > + .name = DRIVER_NAME, > > + .of_match_table = ti_iodelay_of_match, > > + }, > > +}; > > +module_platform_driver(ti_iodelay_driver); > > + > > +MODULE_AUTHOR("Texas Instruments, Inc."); > > +MODULE_DESCRIPTION("Pinconf driver for TI's IO Delay module"); > > +MODULE_LICENSE("GPL v2"); > > And these three macros will be preprocessed away. > > (Perhaps there are non-modular alternatives to MODULE_DEVICE_TABLE and > module_platform_driver, but I'm not sure how those two macros actually > work.) This should be just made to work as a regular loadable module. We already have pinctrl-single for the same platforms working as a loadable module. Regards, Tony -- To unsubscribe from this list: send the line "unsubscribe linux-omap" in the body of a message to majordomo@vger.kernel.org More majordomo info at http://vger.kernel.org/majordomo-info.html
On 03/04/2015 04:58 PM, Tony Lindgren wrote: > * Paul Bolle <pebolle@tiscali.nl> [150304 14:58]: >> Nishanth Menon schreef op di 03-03-2015 om 18:00 [-0600]: >>> diff --git a/drivers/pinctrl/Kconfig b/drivers/pinctrl/Kconfig >>> index ee9f44ad7f02..8e463d75fbb2 100644 >>> --- a/drivers/pinctrl/Kconfig >>> +++ b/drivers/pinctrl/Kconfig >>> @@ -160,6 +160,17 @@ config PINCTRL_TEGRA_XUSB >>> select PINCONF >>> select PINMUX >>> >>> +config PINCTRL_TI_IODELAY >>> + bool "TI IODelay Module pinconf driver" >> >> This adds a boolean Kconfig symbol. Uggh.. thanks for the review and catching this.. >> >>> + 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. >>> + >>> config PINCTRL_TZ1090 >>> bool "Toumaz Xenif TZ1090 pin control driver" >>> depends on SOC_TZ1090 >>> diff --git a/drivers/pinctrl/Makefile b/drivers/pinctrl/Makefile >>> index 0475206dd600..e441cd71aedf 100644 >>> --- a/drivers/pinctrl/Makefile >>> +++ b/drivers/pinctrl/Makefile >>> @@ -28,6 +28,7 @@ obj-$(CONFIG_PINCTRL_TEGRA30) += pinctrl-tegra30.o >>> obj-$(CONFIG_PINCTRL_TEGRA114) += pinctrl-tegra114.o >>> obj-$(CONFIG_PINCTRL_TEGRA124) += pinctrl-tegra124.o >>> obj-$(CONFIG_PINCTRL_TEGRA_XUSB) += pinctrl-tegra-xusb.o >>> +obj-$(CONFIG_PINCTRL_TI_IODELAY) += pinctrl-ti-iodelay.o >> >> So this objectfile will either be built-in or not be built at all. >> >>> obj-$(CONFIG_PINCTRL_TZ1090) += pinctrl-tz1090.o >>> obj-$(CONFIG_PINCTRL_TZ1090_PDC) += pinctrl-tz1090-pdc.o >>> obj-$(CONFIG_PINCTRL_U300) += pinctrl-u300.o >>> diff --git a/drivers/pinctrl/pinctrl-ti-iodelay.c b/drivers/pinctrl/pinctrl-ti-iodelay.c >>> new file mode 100644 >>> index 000000000000..e4c6e25a781c >>> --- /dev/null >>> +++ b/drivers/pinctrl/pinctrl-ti-iodelay.c >>> @@ -0,0 +1,963 @@ >>> [...] >>> +#include <linux/err.h> >>> +#include <linux/init.h> >>> +#include <linux/io.h> >>> +#include <linux/list.h> >>> +#include <linux/module.h> >> >> This is, I think, a bit of a red flag. >> >>> +#include <linux/of_device.h> >>> +#include <linux/of.h> >>> +#include <linux/pinctrl/pinconf-generic.h> >>> +#include <linux/pinctrl/pinconf.h> >>> +#include <linux/pinctrl/pinctrl.h> >>> +#include <linux/regmap.h> >>> +#include <linux/slab.h> >> >> [...] >> >>> +static const struct of_device_id ti_iodelay_of_match[] = { >>> + {.compatible = "ti,dra7-iodelay", .data = &dra7_iodelay_data}, >>> + { /* Hopefully no more.. */ }, >>> +}; >>> +MODULE_DEVICE_TABLE(of, ti_iodelay_of_match); >>> + >>> +static struct platform_driver ti_iodelay_driver = { >>> + .probe = ti_iodelay_probe, >>> + .remove = ti_iodelay_remove, >>> + .driver = { >>> + .owner = THIS_MODULE, >> >> So THIS_MODULE will, basically, be NULL. >> >>> + .name = DRIVER_NAME, >>> + .of_match_table = ti_iodelay_of_match, >>> + }, >>> +}; >>> +module_platform_driver(ti_iodelay_driver); >>> + >>> +MODULE_AUTHOR("Texas Instruments, Inc."); >>> +MODULE_DESCRIPTION("Pinconf driver for TI's IO Delay module"); >>> +MODULE_LICENSE("GPL v2"); >> >> And these three macros will be preprocessed away. >> >> (Perhaps there are non-modular alternatives to MODULE_DEVICE_TABLE and >> module_platform_driver, but I'm not sure how those two macros actually >> work.) > > This should be just made to work as a regular loadable module. > We already have pinctrl-single for the same platforms working > as a loadable module. > Yes, indeed. this can easily be fixed. my bad. will convert this to a module in the next rev. I will wait a week before reposting next rev to see if there are improvements necessary in other areas - specifically in the way pinconf is being used and if there are any binding related comments. Will be great to hear any feedback on the same as well.
On Wed, Mar 4, 2015 at 1:00 AM, Nishanth Menon <nm@ti.com> 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@ti.com> > Signed-off-by: Lokesh Vutla <lokeshvutla@ti.com> 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. Yours, Linus Walleij -- To unsubscribe from this list: send the line "unsubscribe linux-omap" in the body of a message to majordomo@vger.kernel.org More majordomo info at http://vger.kernel.org/majordomo-info.html
On Tue, Mar 10, 2015 at 6:03 AM, Linus Walleij <linus.walleij@linaro.org> wrote: > On Wed, Mar 4, 2015 at 1:00 AM, Nishanth Menon <nm@ti.com> 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@ti.com> >> Signed-off-by: Lokesh Vutla <lokeshvutla@ti.com> > > 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.
diff --git a/drivers/pinctrl/Kconfig b/drivers/pinctrl/Kconfig index ee9f44ad7f02..8e463d75fbb2 100644 --- a/drivers/pinctrl/Kconfig +++ b/drivers/pinctrl/Kconfig @@ -160,6 +160,17 @@ config PINCTRL_TEGRA_XUSB select PINCONF select PINMUX +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. + config PINCTRL_TZ1090 bool "Toumaz Xenif TZ1090 pin control driver" depends on SOC_TZ1090 diff --git a/drivers/pinctrl/Makefile b/drivers/pinctrl/Makefile index 0475206dd600..e441cd71aedf 100644 --- a/drivers/pinctrl/Makefile +++ b/drivers/pinctrl/Makefile @@ -28,6 +28,7 @@ obj-$(CONFIG_PINCTRL_TEGRA30) += pinctrl-tegra30.o obj-$(CONFIG_PINCTRL_TEGRA114) += pinctrl-tegra114.o obj-$(CONFIG_PINCTRL_TEGRA124) += pinctrl-tegra124.o obj-$(CONFIG_PINCTRL_TEGRA_XUSB) += pinctrl-tegra-xusb.o +obj-$(CONFIG_PINCTRL_TI_IODELAY) += pinctrl-ti-iodelay.o obj-$(CONFIG_PINCTRL_TZ1090) += pinctrl-tz1090.o obj-$(CONFIG_PINCTRL_TZ1090_PDC) += pinctrl-tz1090-pdc.o obj-$(CONFIG_PINCTRL_U300) += pinctrl-u300.o diff --git a/drivers/pinctrl/pinctrl-ti-iodelay.c b/drivers/pinctrl/pinctrl-ti-iodelay.c new file mode 100644 index 000000000000..e4c6e25a781c --- /dev/null +++ b/drivers/pinctrl/pinctrl-ti-iodelay.c @@ -0,0 +1,963 @@ +/* + * Support for configuration of IO Delay module found on Texas Instruments SoCs + * such as DRA7 + * + * Copyright (C) 2015 Texas Instruments, Inc. + * + * This file is licensed under the terms of the GNU General Public + * License version 2. This program is licensed "as is" without any + * warranty of any kind, whether express or implied. + */ +#include <linux/err.h> +#include <linux/init.h> +#include <linux/io.h> +#include <linux/list.h> +#include <linux/module.h> +#include <linux/of_device.h> +#include <linux/of.h> +#include <linux/pinctrl/pinconf-generic.h> +#include <linux/pinctrl/pinconf.h> +#include <linux/pinctrl/pinctrl.h> +#include <linux/regmap.h> +#include <linux/slab.h> + +#define IODELAY_REG_NAME_LEN ((sizeof(u32) * 2) + 3) +#define DRIVER_NAME "ti-io-delay" +/* Should I change this? Abuse? */ +#define IODELAY_MUX_PINS_NAME "pinctrl-single,pins" + +/* Device tree match, populated later */ +static const struct of_device_id ti_iodelay_of_match[]; + +/** + * 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; +}; + +/** + * struct ti_iodelay_reg_data - Describes the registers for the IOdelay instance + * @signature_mask: Conf reg- mask for the signature bits + * @signature_value: Conf reg- signature value to be written (see TRM) + * @lock_mask: Conf reg- mask for the lock bits + * @lock_val: Conf reg- lock value for the lock bits (see TRM) + * @unlock_val: Conf reg- unlock value for the lock bits (see TRM) + * @binary_data_coarse_mask: Conf reg- coarse mask (see TRM) + * @binary_data_fine_mask: Conf reg- fine mask (see TRM) + * @reg_refclk_offset: Refclk register offset + * @refclk_period_mask: Refclk mask + * @reg_coarse_offset: Coarse register configuration offset + * @coarse_delay_count_mask: Coarse delay count mask + * @coarse_ref_count_mask: Coarse ref count mask + * @reg_fine_offset: Fine register configuration offset + * @fine_delay_count_mask: Fine delay count mask + * @fine_ref_count_mask: Fine ref count mask + * @reg_global_lock_offset: Global(for the IOdelay module) lock register offset + * @global_lock_mask: Lock mask + * @global_unlock_val: unlock value + * @global_lock_val: lock value + * @reg_start_offset: Where does the configuration registers start? + * @regmap_config: Regmap configuration for the IODelay region + */ +struct ti_iodelay_reg_data { + u32 signature_mask; + u32 signature_value; + u32 lock_mask; + u32 lock_val; + u32 unlock_val; + u32 binary_data_coarse_mask; + u32 binary_data_fine_mask; + + u32 reg_refclk_offset; + u32 refclk_period_mask; + + u32 reg_coarse_offset; + u32 coarse_delay_count_mask; + u32 coarse_ref_count_mask; + + u32 reg_fine_offset; + u32 fine_delay_count_mask; + u32 fine_ref_count_mask; + + u32 reg_global_lock_offset; + u32 global_lock_mask; + u32 global_unlock_val; + u32 global_lock_val; + + u32 reg_start_offset; + + struct regmap_config *regmap_config; +}; + +/** + * 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; + u16 coarse_delay_count; + + u16 fine_ref_count; + 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]; +}; + +/** + * struct ti_iodelay_pingroup - Structure that describes one group + * @np: Node pointer (device tree) + * @name: Name of the group + * @map: pinctrl map allocated for the group + * @vals: configuration values allocated for the group (from dt) + * @nvals: number of configuration values allocated + * @config: pinconf "Config" - currently a dummy value + * @node: list node to next group + */ +struct ti_iodelay_pingroup { + struct device_node *np; + const char *name; + struct pinctrl_map *map; + struct ti_iodelay_conf_vals *vals; + int nvals; + unsigned long config; + struct list_head node; +}; + +/** + * 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; + + struct list_head groups; + int ngroups; + struct mutex mutex; /* list protection */ + + const struct ti_iodelay_reg_data *reg_data; + struct ti_iodelay_reg_values reg_init_conf_values; +}; + +/*--- IOdelay configuration stuff ----*/ + +/** + * ti_iodelay_extract() - extract bits for a field + * @val: register value + * @mask: Mask + * + * Return: extracted value which is appropriately shifted + */ +static inline u32 ti_iodelay_extract(u32 val, u32 mask) +{ + return (val & mask) >> __ffs(mask); +} + +/** + * ti_iodelay_compute_dpe() - Compute equation for delay parameter + * @period: Period to use + * @ref: Reference Count + * @delay: Delay count + * @delay_m: Delay multiplier + * + * Return: Computed delay parameter + */ +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); +} + +/** + * 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; +} + +/** + * ti_iodelay_pinconf_init_dev() - Initialize IODelay device + * @iod: IODelay device + * + * Unlocks the IODelay region, computes the common parameters + * + * Return: 0 in case of success, else appropriate error value + */ +static int ti_iodelay_pinconf_init_dev(struct ti_iodelay_device *iod) +{ + const struct ti_iodelay_reg_data *reg = iod->reg_data; + struct device *dev = iod->dev; + struct ti_iodelay_reg_values *ival = &iod->reg_init_conf_values; + u32 val; + int r; + + /* 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); + + 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); + if (!ival->cdpe) { + dev_err(dev, "Invalid cdpe computed params = %d %d %d\n", + ival->ref_clk_period, ival->coarse_ref_count, + ival->coarse_delay_count); + return -EINVAL; + } + dev_dbg(iod->dev, "coarse: ref=0x%04x delay=0x%04x cdpe=0x%08x\n", + ival->coarse_ref_count, ival->coarse_delay_count, ival->cdpe); + + r = regmap_read(iod->regmap, reg->reg_fine_offset, &val); + if (r) + return r; + ival->fine_ref_count = + ti_iodelay_extract(val, reg->fine_ref_count_mask); + ival->fine_delay_count = + ti_iodelay_extract(val, reg->fine_delay_count_mask); + if (!ival->fine_delay_count) { + dev_err(dev, "Invalid Fine delay count (0) (reg=0x%08x)\n", + val); + return -EINVAL; + } + ival->fdpe = ti_iodelay_compute_dpe(ival->ref_clk_period, + ival->fine_ref_count, + ival->fine_delay_count, 264); + if (!ival->fdpe) { + dev_err(dev, "Invalid fdpe(0) computed params = %d %d %d\n", + ival->ref_clk_period, ival->fine_ref_count, + ival->fine_delay_count); + return -EINVAL; + } + dev_dbg(iod->dev, "fine: ref=0x%04x delay=0x%04x fdpe=0x%08x\n", + ival->fine_ref_count, ival->fine_delay_count, ival->fdpe); + + return 0; +} + +/** + * ti_iodelay_pinconf_deinit_dev() - deinit the IOdelay device + * @iod: IODelay device + * + * Deinitialize the IODelay device (basically just lock the region back up. + */ +static void ti_iodelay_pinconf_deinit_dev(struct ti_iodelay_device *iod) +{ + const struct ti_iodelay_reg_data *reg = iod->reg_data; + + /* lock the IOdelay region back again */ + regmap_update_bits(iod->regmap, reg->reg_global_lock_offset, + reg->global_lock_mask, reg->global_lock_val); +} + +/*--- Pinctrl/pinconf framework stuff ----*/ + +/** + * ti_iodelay_get_group() - Find the group mapped by a group selector + * @iod: IODelay device + * @gselector: Group Selector + * + * Return: Corresponding group representing group selector in list of groups + * managed in IOdelay device OR NULL if not found. + */ +static struct ti_iodelay_pingroup *ti_iodelay_get_group(struct ti_iodelay_device + *iod, + unsigned gselector) +{ + struct ti_iodelay_pingroup *group; + int gid = 0; + + list_for_each_entry(group, &iod->groups, node) { + if (gid == gselector) + return group; + gid++; + } + + dev_err(iod->dev, "%s could not find pingroup %i\n", __func__, + gselector); + return NULL; +} + +/** + * ti_iodelay_dt_node_to_map() - Map a device tree node to appropriate group + * @pctldev: pinctrl device representing IODelay device + * @np: Node Pointer (device tree) + * @map: Pinctrl Map returned back to pinctrl framework + * @num_maps: Number of maps (1) + * + * Maps the device tree description into a group of configuration parameters + * for IOdelay block entry. + * + * Return: 0 in case of success, else appropriate error value + */ +static int ti_iodelay_dt_node_to_map(struct pinctrl_dev *pctldev, + struct device_node *np, + struct pinctrl_map **map, + unsigned *num_maps) +{ + struct ti_iodelay_device *iod; + struct device *dev; + /* const char **pgnames; */ + int ret = 0; + const __be32 *mux; + struct ti_iodelay_conf_vals *vals; + struct ti_iodelay_pingroup *group; + int size, index, idx, rows; + u32 offset, val; + + iod = pinctrl_dev_get_drvdata(pctldev); + if (!iod) + return -EINVAL; + dev = iod->dev; + + *map = devm_kzalloc(dev, sizeof(**map), GFP_KERNEL); + if (!*map) + return -ENOMEM; + *num_maps = 0; + + group = devm_kzalloc(dev, sizeof(*group), GFP_KERNEL); + if (!group) { + ret = -ENOMEM; + goto free_map; + } + + mux = of_get_property(np, IODELAY_MUX_PINS_NAME, &size); + if ((!mux) || (size < sizeof(*mux) * 2)) { + dev_err(dev, "bad data for mux %s\n", np->name); + ret = -EINVAL; + goto free_group; + } + + size /= sizeof(*mux); /* Number of elements in array */ + rows = size / 2; + + vals = devm_kzalloc(dev, sizeof(*vals) * rows, GFP_KERNEL); + if (!vals) { + ret = -ENOMEM; + goto free_group; + } + + index = 0; + idx = 0; + while (index < size) { + offset = be32_to_cpup(mux + index++); + val = be32_to_cpup(mux + index++); + vals[idx].offset = offset; + vals[idx].a_delay = val & 0xFFFF; + vals[idx].g_delay = (val & 0xFFFF0000) >> 16; + if (offset > iod->reg_data->regmap_config->max_register) { + dev_err(dev, "Invalid offset for %s 0x%x\n", + np->name, offset); + break; + } + dev_dbg(dev, "%s offset=%x a_delay = %d g_delay = %d\n", + np->name, vals[idx].offset, vals[idx].a_delay, + vals[idx].g_delay); + idx++; + } + + group->name = np->name; + group->np = np; + group->vals = vals; + group->nvals = idx; + group->config = PIN_CONFIG_END; + group->map = *map; + + /* Add to group list */ + mutex_lock(&iod->mutex); + list_add_tail(&group->node, &iod->groups); + iod->ngroups++; + mutex_unlock(&iod->mutex); + + (*map)->type = PIN_MAP_TYPE_CONFIGS_GROUP; + (*map)->data.configs.group_or_pin = np->name; + (*map)->data.configs.configs = &group->config; + (*map)->data.configs.num_configs = 1; + *num_maps = 1; + + return 0; + +free_group: + devm_kfree(dev, group); +free_map: + devm_kfree(dev, *map); + return ret; +} + +/** + * ti_iodelay_dt_free_map() - Free map and resource alloted as per the map + * @pctldev: pinctrl device representing IODelay device + * @map: Map allocated by ti_iodelay_dt_node_to_map + * @num_maps: Num maps (1) + * + * Removes the group associated with the map and frees all resources allocated + * for the group. + */ +static void ti_iodelay_dt_free_map(struct pinctrl_dev *pctldev, + struct pinctrl_map *map, unsigned num_maps) +{ + struct ti_iodelay_device *iod; + struct device *dev; + struct ti_iodelay_pingroup *group; + bool found = false; + + if (!map) + return; + + iod = pinctrl_dev_get_drvdata(pctldev); + if (!iod) + return; + dev = iod->dev; + + mutex_lock(&iod->mutex); + list_for_each_entry(group, &iod->groups, node) { + if (group->map == map) { + found = true; + list_del(&group->node); + iod->ngroups--; + break; + } + } + mutex_unlock(&iod->mutex); + + /* If some freaky pinconf framework bug... */ + if (!found) + return; + + devm_kfree(dev, group->vals); + devm_kfree(dev, group); + devm_kfree(dev, map); +} + +/** + * ti_iodelay_pinctrl_get_groups_count() - Get number of groups registered + * @pctldev: pinctrl device representing IODelay device + * + * Return: number of groups mapped on the IODelay + */ +static int ti_iodelay_pinctrl_get_groups_count(struct pinctrl_dev *pctldev) +{ + struct ti_iodelay_device *iod; + struct device *dev; + + iod = pinctrl_dev_get_drvdata(pctldev); + dev = iod->dev; + + return iod->ngroups; +} + +/** + * ti_iodelay_pinctrl_get_group_name() - Get the group name + * @pctldev: pinctrl device representing IODelay device + * @gselector: group selector + * + * Return: name of the Group given a valid gselector, else NULL. + */ +static const char *ti_iodelay_pinctrl_get_group_name(struct pinctrl_dev + *pctldev, + unsigned gselector) +{ + struct ti_iodelay_device *iod; + struct device *dev; + struct ti_iodelay_pingroup *group; + + iod = pinctrl_dev_get_drvdata(pctldev); + dev = iod->dev; + + group = ti_iodelay_get_group(iod, gselector); + if (!group) + return NULL; + + return group->name; +} + +/** + * 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; +} + +/** + * ti_iodelay_pinconf_group_get() - Get the group configuration + * @pctldev: pinctrl device representing IODelay device + * @gselector: Group selector + * @config: configuration returned + * + * Return: The configuration if the group is valid, else returns -EINVAL + */ +static int ti_iodelay_pinconf_group_get(struct pinctrl_dev *pctldev, + unsigned gselector, + unsigned long *config) +{ + struct ti_iodelay_device *iod; + struct device *dev; + struct ti_iodelay_pingroup *group; + + iod = pinctrl_dev_get_drvdata(pctldev); + dev = iod->dev; + group = ti_iodelay_get_group(iod, gselector); + + if (!group) + return -EINVAL; + + *config = group->config; + return 0; +} + +/** + * ti_iodelay_pinconf_group_set() - Configure the groups of pins + * @pctldev: pinctrl device representing IODelay device + * @gselector: Group selector + * @configs: Configurations + * @num_configs: Number of configurations + * + * Return: 0 if all went fine, else appropriate error value. + */ +static int ti_iodelay_pinconf_group_set(struct pinctrl_dev *pctldev, + unsigned gselector, + unsigned long *configs, + unsigned num_configs) +{ + struct ti_iodelay_device *iod; + struct device *dev; + struct ti_iodelay_pingroup *group; + int i; + + iod = pinctrl_dev_get_drvdata(pctldev); + dev = iod->dev; + group = ti_iodelay_get_group(iod, gselector); + + if (num_configs != 1) { + dev_err(dev, "Unsupported number of configurations %d\n", + num_configs); + return -EINVAL; + } + + if (*configs != PIN_CONFIG_END) { + dev_err(dev, "Unsupported configuration\n"); + return -EINVAL; + } + + for (i = 0; i < group->nvals; i++) { + if (ti_iodelay_pinconf_set(iod, &group->vals[i])) + return -ENOTSUPP; + } + + return 0; +} + +#ifdef CONFIG_DEBUG_FS +/** + * ti_iodelay_pinconf_group_dbg_show() - show the group information + * @pctldev: Show the group information + * @s: Sequence file + * @gselector: group selector + * + * Provide the configuration information of the selected group + */ +static void ti_iodelay_pinconf_group_dbg_show(struct pinctrl_dev *pctldev, + struct seq_file *s, + unsigned gselector) +{ + struct ti_iodelay_device *iod; + struct device *dev; + struct ti_iodelay_pingroup *group; + int i; + + iod = pinctrl_dev_get_drvdata(pctldev); + dev = iod->dev; + group = ti_iodelay_get_group(iod, gselector); + if (!group) + return; + + for (i = 0; i < group->nvals; i++) { + struct ti_iodelay_conf_vals *val; + u32 reg = 0; + + val = &group->vals[i]; + regmap_read(iod->regmap, val->offset, ®), + seq_printf(s, "\n\t0x%08x = 0x%08x (%3d, %3d)", + val->offset, reg, val->a_delay, val->g_delay); + } +} +#endif + +static struct pinctrl_ops ti_iodelay_pinctrl_ops = { + .dt_node_to_map = ti_iodelay_dt_node_to_map, + .dt_free_map = ti_iodelay_dt_free_map, + .get_groups_count = ti_iodelay_pinctrl_get_groups_count, + .get_group_name = ti_iodelay_pinctrl_get_group_name, + .get_group_pins = ti_iodelay_pinctrl_get_group_pins, +}; + +static struct pinconf_ops ti_iodelay_pinctrl_pinconf_ops = { + .pin_config_group_get = ti_iodelay_pinconf_group_get, + .pin_config_group_set = ti_iodelay_pinconf_group_set, +#ifdef CONFIG_DEBUG_FS + .pin_config_group_dbg_show = ti_iodelay_pinconf_group_dbg_show, +#endif +}; + +/** + * ti_iodelay_alloc_pins() - Allocate structures needed for pins for IOdelay + * @dev: device pointer + * @iod: IODelay device + * @base_phy: Base Physical Address + * + * Return: 0 if all went fine, else appropriate error value. + */ +static int ti_iodelay_alloc_pins(struct device *dev, + struct ti_iodelay_device *iod, u32 base_phy) +{ + const struct ti_iodelay_reg_data *r = iod->reg_data; + struct pinctrl_pin_desc *pin; + struct ti_iodelay_pin_name *pn; + u32 phy_reg; + int nr_pins, i; + + nr_pins = (r->regmap_config->max_register - r->reg_start_offset) / 4; + + dev_dbg(dev, "Allocating %i pins\n", nr_pins); + + iod->pa = devm_kzalloc(dev, sizeof(*iod->pa) * nr_pins, GFP_KERNEL); + if (!iod->pa) + return -ENOMEM; + + iod->names = + devm_kzalloc(dev, sizeof(struct ti_iodelay_pin_name) * nr_pins, + GFP_KERNEL); + if (!iod->names) + return -ENOMEM; + + iod->desc.pins = iod->pa; + iod->desc.npins = nr_pins; + + phy_reg = r->reg_start_offset + base_phy; + pn = iod->names; + for (i = 0; i < nr_pins; i++, pn++, phy_reg += 4) { + pin = &iod->pa[i]; + sprintf(pn->name, "%x.%d", phy_reg, i); + pin->number = i; + pin->name = pn->name; + } + + return 0; +} + +/** + * ti_iodelay_probe() - Standard probe + * @pdev: platform device + * + * Return: 0 if all went fine, else appropriate error value. + */ +static int ti_iodelay_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct device_node *np = of_node_get(dev->of_node); + const struct of_device_id *match; + struct resource *res; + struct ti_iodelay_device *iod; + int ret = 0; + + if (!np) { + ret = -EINVAL; + dev_err(dev, "No OF node\n"); + goto exit_out; + } + + match = of_match_device(ti_iodelay_of_match, dev); + if (!match) { + ret = -EINVAL; + dev_err(dev, "No DATA match\n"); + goto exit_out; + } + + iod = devm_kzalloc(dev, sizeof(*iod), GFP_KERNEL); + if (!iod) { + ret = -ENOMEM; + goto exit_out; + } + iod->dev = dev; + iod->reg_data = match->data; + + /* So far We can assume there is only 1 bank of registers */ + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!res) { + dev_err(dev, "Missing MEM resource\n"); + ret = -ENODEV; + goto exit_out; + } + + iod->reg_base = devm_ioremap_resource(dev, res); + if (IS_ERR(iod->reg_base)) { + ret = PTR_ERR(iod->reg_base); + goto exit_out; + } + + iod->regmap = devm_regmap_init_mmio(dev, iod->reg_base, + iod->reg_data->regmap_config); + if (IS_ERR(iod->regmap)) { + dev_err(dev, "Regmap MMIO init failed.\n"); + ret = PTR_ERR(iod->regmap); + goto exit_out; + } + + if (ti_iodelay_pinconf_init_dev(iod)) + goto exit_out; + + ret = ti_iodelay_alloc_pins(dev, iod, res->start); + if (ret) + goto exit_out; + + INIT_LIST_HEAD(&iod->groups); + mutex_init(&iod->mutex); + + iod->desc.pctlops = &ti_iodelay_pinctrl_ops; + /* no pinmux ops - we are pinconf */ + iod->desc.confops = &ti_iodelay_pinctrl_pinconf_ops; + iod->desc.name = dev_name(dev); + iod->desc.owner = THIS_MODULE; + + iod->pctl = pinctrl_register(&iod->desc, dev, iod); + if (!iod->pctl) { + dev_err(dev, "Failed to register pinctrl\n"); + ret = -ENODEV; + goto exit_out; + } + + platform_set_drvdata(pdev, iod); + +exit_out: + of_node_put(np); + return ret; +} + +/** + * ti_iodelay_remove() - standard remove + * @pdev: platform device + * + * Return: 0 if all went fine, else appropriate error value. + */ +static int ti_iodelay_remove(struct platform_device *pdev) +{ + struct ti_iodelay_device *iod = platform_get_drvdata(pdev); + + if (!iod) + return 0; + if (iod->pctl) + pinctrl_unregister(iod->pctl); + + ti_iodelay_pinconf_deinit_dev(iod); + + /* Expect other allocations to be freed by devm */ + + return 0; +} + +static struct regmap_config dra7_iodelay_regmap_config = { + .reg_bits = 32, + .reg_stride = 4, + .val_bits = 32, + .max_register = 0xD1C, +}; + +static struct ti_iodelay_reg_data dra7_iodelay_data = { + .signature_mask = 0x0003F000, + .signature_value = 0x29, + .lock_mask = 0x00000400, + .lock_val = 1, + .unlock_val = 0, + .binary_data_coarse_mask = 0x000003E0, + .binary_data_fine_mask = 0x0000001F, + + .reg_refclk_offset = 0x14, + .refclk_period_mask = 0xFFFF, + + .reg_coarse_offset = 0x18, + .coarse_delay_count_mask = 0xFFFF0000, + .coarse_ref_count_mask = 0x0000FFFF, + + .reg_fine_offset = 0x1C, + .fine_delay_count_mask = 0xFFFF0000, + .fine_ref_count_mask = 0x0000FFFF, + + .reg_global_lock_offset = 0x2C, + .global_lock_mask = 0x0000FFFF, + .global_unlock_val = 0x0000AAAA, + .global_lock_val = 0x0000AAAB, + + .reg_start_offset = 0x30, + .regmap_config = &dra7_iodelay_regmap_config, +}; + +static const struct of_device_id ti_iodelay_of_match[] = { + {.compatible = "ti,dra7-iodelay", .data = &dra7_iodelay_data}, + { /* Hopefully no more.. */ }, +}; +MODULE_DEVICE_TABLE(of, ti_iodelay_of_match); + +static struct platform_driver ti_iodelay_driver = { + .probe = ti_iodelay_probe, + .remove = ti_iodelay_remove, + .driver = { + .owner = THIS_MODULE, + .name = DRIVER_NAME, + .of_match_table = ti_iodelay_of_match, + }, +}; +module_platform_driver(ti_iodelay_driver); + +MODULE_AUTHOR("Texas Instruments, Inc."); +MODULE_DESCRIPTION("Pinconf driver for TI's IO Delay module"); +MODULE_LICENSE("GPL v2"); diff --git a/include/dt-bindings/pinctrl/dra.h b/include/dt-bindings/pinctrl/dra.h index 7448edff4723..0a26495c4df5 100644 --- a/include/dt-bindings/pinctrl/dra.h +++ b/include/dt-bindings/pinctrl/dra.h @@ -30,6 +30,8 @@ #define MUX_MODE14 0xe #define MUX_MODE15 0xf +#define MANUAL_MODE MODE_SELECT + #define PULL_ENA (0 << 16) #define PULL_DIS (1 << 16) #define PULL_UP (1 << 17) @@ -47,5 +49,9 @@ #define PIN_INPUT_PULLUP (PULL_ENA | INPUT_EN | PULL_UP) #define PIN_INPUT_PULLDOWN (PULL_ENA | INPUT_EN) +/* DRA7 IODELAY configuration parameters */ +#define A_DELAY(val) ((val) & 0xFFFF) +#define G_DELAY(val) (((val) & 0xFFFF) << 16) + #endif