All of lore.kernel.org
 help / color / mirror / Atom feed
From: Tomasz Figa <tomasz.figa@gmail.com>
To: Doug Anderson <dianders@chromium.org>
Cc: Kukjin Kim <kgene.kim@samsung.com>,
	Olof Johansson <olof@lixom.net>,
	Stephen Warren <swarren@wwwdotorg.org>,
	Thomas Abraham <thomas.abraham@linaro.org>,
	Linus Walleij <linus.walleij@linaro.org>,
	Prathyush K <prathyush.k@samsung.com>,
	linux-samsung-soc@vger.kernel.org, linux-kernel@vger.kernel.org
Subject: Re: [PATCH 1/2] pinctrl: samsung: fix suspend/resume functionality
Date: Thu, 16 May 2013 21:19:20 +0200	[thread overview]
Message-ID: <1944811.006SLH6aYj@flatron> (raw)
In-Reply-To: <1368724352-10849-2-git-send-email-dianders@chromium.org>

Hi Doug,

Thanks for the patch. See my comments inline.

On Thursday 16 of May 2013 10:12:31 Doug Anderson wrote:
> The GPIO states need to be restored after s2r and this is not currently
> supported in the pinctrl driver. This patch saves the gpio states before
> suspend and restores them after resume.
> 
> Logic and commenting for samsung_pinctrl_resume_noirq() is heavily
> borrowed from the old samsung_gpio_pm_2bit_resume(), which seemed to
> do this a reasonable way.
> 
> Patch originally from Prathyush K <prathyush.k@samsung.com> but
> rewritten by Doug Anderson <dianders@chromium.org>.
> 
> Signed-off-by: Prathyush K <prathyush.k@samsung.com>
> Signed-off-by: Doug Anderson <dianders@chromium.org>
> ---
>  drivers/pinctrl/pinctrl-samsung.c | 199
> ++++++++++++++++++++++++++++++++++++++
> drivers/pinctrl/pinctrl-samsung.h |  11 +++
>  2 files changed, 210 insertions(+)
> 
> diff --git a/drivers/pinctrl/pinctrl-samsung.c
> b/drivers/pinctrl/pinctrl-samsung.c index 9763668..0891667 100644
> --- a/drivers/pinctrl/pinctrl-samsung.c
> +++ b/drivers/pinctrl/pinctrl-samsung.c
> @@ -964,6 +964,204 @@ static int samsung_pinctrl_probe(struct
> platform_device *pdev) return 0;
>  }
> 
> +#ifdef CONFIG_PM
> +
> +/**
> + * samsung_pinctrl_resume_noirq - save pinctrl state for suspend
> + *
> + * Save data for all banks handled by this device.
> + */
> +static int samsung_pinctrl_suspend_noirq(struct device *dev)
> +{
> +	struct samsung_pinctrl_drv_data *drvdata = dev_get_drvdata(dev);
> +	struct samsung_pin_ctrl *ctrl = drvdata->ctrl;
> +	void __iomem * const virt_base = drvdata->virt_base;

Nit: This const is ugly :) . Is it needed for anything?

> +	int i;
> +
> +	for (i = 0; i < ctrl->nr_banks; i++) {
> +		struct samsung_pin_bank *bank = &ctrl->pin_banks[i];
> +		const struct samsung_pin_bank_type *type = bank->type;
> +		void __iomem * const reg = virt_base + bank->pctl_offset;

Nit: This one is not pretty either.

> +		bank->pm_save.con = readl(reg +
> +					  type-
>reg_offset[PINCFG_TYPE_FUNC]);
> +		if (type->fld_width[PINCFG_TYPE_FUNC] > 4)

What is this condition supposed to check?

> +			bank->pm_save.con |= (u64)readl(reg + 4 +
> +				      type->reg_offset[PINCFG_TYPE_FUNC]) 
<< 32;

This looks ugly. Whatever is going on here, wouldn't it be better to use 
separate field, like con2 or something?

> +		bank->pm_save.dat = readl(reg +
> +					  type-
>reg_offset[PINCFG_TYPE_DAT]);
> +		bank->pm_save.pud = readl(reg +
> +					  type-
>reg_offset[PINCFG_TYPE_PUD]);
> +		bank->pm_save.drv = readl(reg +
> +					  type-
>reg_offset[PINCFG_TYPE_DRV]);
> +
> +		if (type->fld_width[PINCFG_TYPE_CON_PDN]) {
> +			bank->pm_save.conpdn = readl(reg +
> +				type->reg_offset[PINCFG_TYPE_CON_PDN]);
> +			bank->pm_save.pudpdn = readl(reg +
> +				type->reg_offset[PINCFG_TYPE_PUD_PDN]);
> +		}

I wonder if you couldn't do all the saving here in a single loop over all 
pin control types, like:

	unsigned int offsets = bank->type->reg_offsets;
	unsigned int widths = bank->type->fld_width;

	for (i = 0; i < PINCFG_TYPE_NUM; ++i)
		if (widths[i])
			bank->pm_save[i] = readl(reg + offsets[i]);

The only thing not handled by this loop is second CON registers in banks 
with two of them. I can't think of any better solution for this other than 
just adding a special case after the loop.

> +		dev_dbg(dev, "Save %s @ %p (con %#010llx)\n",
> +			bank->name, reg, bank->pm_save.con);
> +	}
> +
> +	return 0;
> +}
> +
> +/**
> + * is_sfn - test whether a pin config represents special function.
> + *
> + * Test whether the given masked+shifted bits of an GPIO configuration
> + * are one of the SFN (special function) modes.
> + */
> +static inline int is_sfn(u32 con)
> +{
> +	return con >= 2;
> +}
> +
> +/**
> + * is_in - test if the given masked+shifted GPIO configuration is an
> input. + */
> +static inline int is_in(u32 con)
> +{
> +	return con == 0;
> +}
> +
> +/**
> + * is_out - test if the given masked+shifted GPIO configuration is an
> output. + */
> +static inline int is_out(u32 con)
> +{
> +	return con == 1;
> +}
> +
> +/**
> + * samsung_pinctrl_resume_noirq - restore pinctrl state from suspend
> + *
> + * Restore one of the GPIO banks that was saved during suspend. This is
> + * not as simple as once thought, due to the possibility of glitches +
> * from the order that the CON and DAT registers are set in.
> + *
> + * The three states the pin can be are {IN,OUT,SFN} which gives us 9
> + * combinations of changes to check. Three of these, if the pin stays
> + * in the same configuration can be discounted. This leaves us with
> + * the following:
> + *
> + * { IN => OUT }  Change DAT first
> + * { IN => SFN }  Change CON first
> + * { OUT => SFN } Change CON first, so new data will not glitch
> + * { OUT => IN }  Change CON first, so new data will not glitch
> + * { SFN => IN }  Change CON first
> + * { SFN => OUT } Change DAT first, so new data will not glitch [1]
> + *
> + * We do not currently deal with the UP registers as these control
> + * weak resistors, so a small delay in change should not need to bring
> + * these into the calculations.
> + *
> + * [1] this assumes that writing to a pin DAT whilst in SFN will set
> the + *     state for when it is next output.
> + */
> +static int samsung_pinctrl_resume_noirq(struct device *dev)
> +{
> +	struct samsung_pinctrl_drv_data *drvdata = dev_get_drvdata(dev);
> +	struct samsung_pin_ctrl *ctrl = drvdata->ctrl;
> +	void __iomem * const virt_base = drvdata->virt_base;
> +	int i;
> +
> +	for (i = 0; i < ctrl->nr_banks; i++) {
> +		const struct samsung_pin_bank *bank = &ctrl->pin_banks[i];
> +		void __iomem * const reg = virt_base + bank->pctl_offset;
> +		const struct samsung_pin_bank_type *type = bank->type;
> +		const u8 func_offset = type->reg_offset[PINCFG_TYPE_FUNC];
> +		const u32 func_width = type->fld_width[PINCFG_TYPE_FUNC];
> +		const u32 func_mask = (1 << func_width) - 1;

I'm constantly seeing so many consts in this patch. Is this constraint 
relevant here in any way? Git grep doesn't show too many instances of this 
construct used for local variables around the kernel.

> +		const bool is64bit = type->fld_width[PINCFG_TYPE_FUNC] > 
4;

Again this check. Is it supposed to handle banks with two CON registers? 
If yes, it is incorrect, as they have 4 bits per pin.

> +		const u64 to_con = bank->pm_save.con;
> +		u64 from_con = readl(reg + func_offset);
> +		u64 change_mask = 0;
> +		u64 to_write;
> +		int pin;
> +
> +		if (is64bit)
> +			from_con |= (u64)readl(reg + 4 + func_offset) << 
32;

Same comment as in save.

> +
> +		/* Easy--the PUD and DRV can go first */
> +		writel(bank->pm_save.pud,
> +		       reg + type->reg_offset[PINCFG_TYPE_PUD]);
> +		writel(bank->pm_save.drv,
> +		       reg + type->reg_offset[PINCFG_TYPE_DRV]);
> +
> +		/*
> +		 * Create a change_mask of all the items that need to have
> +		 * their CON value changed before their DAT value, so that
> +		 * we minimise the work between the two settings.
> +		 */
> +		for (pin = 0; pin < bank->nr_pins; pin++) {

This is a bit more tricky that in case of save, so CON register as a 
special case is justified here.

> +			u32 shift = pin * func_width;
> +			u32 from_func = (from_con >> shift) & func_mask;
> +			u32 to_func = (to_con >> shift) & func_mask;
> +
> +			/* If there is no change, then skip */
> +			if (from_func == to_func)
> +				continue;
> +
> +			/* If both are special function, then skip */
> +			if (is_sfn(from_func) && is_sfn(to_func))
> +				continue;
> +
> +			/* Change is IN => OUT, do not change now */
> +			if (is_in(from_func) && is_out(to_func))
> +				continue;
> +
> +			/* Change is SFN => OUT, do not change now */
> +			if (is_sfn(from_func) && is_out(to_func))
> +				continue;
> +
> +			/*
> +			 * We should now be at the case of:
> +			 *   IN=>SFN, OUT=>SFN, OUT=>IN, SFN=>IN.
> +			 */
> +			change_mask |= (func_mask << shift);
> +		}
> +
> +		/* Write the new CON settings */
> +		to_write = (from_con & ~change_mask) | (to_con & 
change_mask);
> +		writel((u32)to_write, reg + func_offset);
> +		if (is64bit)
> +			writel((u32)(to_write >> 32), reg + 4 + 
func_offset);
> +
> +		/* Now change any items that require DAT,CON */
> +		writel(bank->pm_save.dat,
> +		       reg + type->reg_offset[PINCFG_TYPE_DAT]);
> +		writel((u32)to_con, reg + func_offset);
> +		if (is64bit)
> +			writel((u32)(to_con >> 32), reg + 4 + to_con);
> +
> +		if (type->fld_width[PINCFG_TYPE_CON_PDN]) {
> +			writel(bank->pm_save.conpdn,
> +			       reg + type-
>reg_offset[PINCFG_TYPE_CON_PDN]);
> +			writel(bank->pm_save.pudpdn,
> +			       reg + type-
>reg_offset[PINCFG_TYPE_PUD_PDN]);
> +		}

Now as I think of it, do CON_PDN and PUD_PDN really need to be saved? I 
couldn't find in the documentation if they are preserved or lost in sleep 
mode. Do you have some information on this?

My experiments on Exynos4210 and 4x12 showed that they are preserved, but 
I don't know what about other SoCs.

> +		dev_dbg(dev, "Restore %s@%p (%#010llx=>%#010llx ch 
%#010llx)\n",
> +			bank->name, reg, from_con, to_con, change_mask);
> +	}
> +
> +	return 0;
> +}

I wonder if the whole restoration procedure couldn't be simplified. I 
don't remember my version being so complicated, but I don't have my patch 
on my screen at the moment, so I might be wrong on this.

> +#else
> +#define samsung_pinctrl_suspend_noirq	NULL
> +#define samsung_pinctrl_resume_noirq	NULL
> +#endif
> +
> +static const struct dev_pm_ops samsung_pinctrl_dev_pm_ops = {
> +	.suspend_noirq = samsung_pinctrl_suspend_noirq,
> +	.resume_noirq = samsung_pinctrl_resume_noirq,
> +};

I'm not sure if resume_noirq is really early enough. Some drivers might 
already need correct pin configuration in their resume_noirq callback.

In my patch I have used syscore_ops to register very late suspend and very 
early resume callbacks for the whole pinctrl-samsung driver and a global 
list of registered pin controllers, that is iterated over in suspend and 
resume.

>  static const struct of_device_id samsung_pinctrl_dt_match[] = {
>  #ifdef CONFIG_PINCTRL_EXYNOS
>  	{ .compatible = "samsung,exynos4210-pinctrl",
> @@ -987,6 +1185,7 @@ static struct platform_driver
> samsung_pinctrl_driver = { .name	= "samsung-pinctrl",
>  		.owner	= THIS_MODULE,
>  		.of_match_table = of_match_ptr(samsung_pinctrl_dt_match),
> +		.pm = &samsung_pinctrl_dev_pm_ops,
>  	},
>  };
> 
> diff --git a/drivers/pinctrl/pinctrl-samsung.h
> b/drivers/pinctrl/pinctrl-samsung.h index 7c7f9eb..c9a7b6e 100644
> --- a/drivers/pinctrl/pinctrl-samsung.h
> +++ b/drivers/pinctrl/pinctrl-samsung.h
> @@ -127,6 +127,7 @@ struct samsung_pin_bank_type {
>   * @gpio_chip: GPIO chip of the bank.
>   * @grange: linux gpio pin range supported by this bank.
>   * @slock: spinlock protecting bank registers
> + * @pm_save: saved register values during suspend
>   */
>  struct samsung_pin_bank {
>  	struct samsung_pin_bank_type *type;
> @@ -144,6 +145,16 @@ struct samsung_pin_bank {
>  	struct gpio_chip gpio_chip;
>  	struct pinctrl_gpio_range grange;
>  	spinlock_t slock;
> +#ifdef CONFIG_PM
> +	struct {
> +		u64	con;
> +		u32	dat;
> +		u32	pud;
> +		u32	drv;
> +		u32	conpdn;
> +		u32	pudpdn;

This could be changed into an array.

Best regards,
Tomasz


  reply	other threads:[~2013-05-16 19:19 UTC|newest]

Thread overview: 25+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2013-05-16 17:12 [PATCH 0/2] Fix suspend/resume issues created by pinmux on exynos Doug Anderson
2013-05-16 17:12 ` [PATCH 1/2] pinctrl: samsung: fix suspend/resume functionality Doug Anderson
2013-05-16 19:19   ` Tomasz Figa [this message]
2013-05-16 20:32     ` Doug Anderson
2013-05-16 21:27       ` Tomasz Figa
2013-05-16 21:51         ` Doug Anderson
2013-05-16 22:08           ` Tomasz Figa
2013-05-16 22:30             ` Heiko Stübner
2013-05-16 22:56               ` Tomasz Figa
2013-05-16 23:10                 ` Doug Anderson
2013-05-16 23:55                   ` Doug Anderson
2013-05-16 21:19     ` Heiko Stübner
2013-05-16 17:12 ` [PATCH 2/2] pinctrl: exynos: fix eint wakeup by using irq_set_wake() Doug Anderson
2013-05-16 19:26   ` Tomasz Figa
2013-05-16 22:25     ` Doug Anderson
2013-05-16 22:37       ` Tomasz Figa
2013-05-16 23:49         ` Doug Anderson
2013-05-16 23:19 ` [PATCH v2] pinctrl: samsung: fix suspend/resume functionality Doug Anderson
2013-05-17  4:33   ` [PATCH v3] " Doug Anderson
2013-05-17 14:38     ` Tomasz Figa
2013-05-20 11:47     ` Linus Walleij
2013-05-20 14:59       ` Doug Anderson
2013-05-20 16:16         ` Kukjin Kim
2013-05-21  6:26         ` Olof Johansson
2013-05-21 11:21     ` Linus Walleij

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=1944811.006SLH6aYj@flatron \
    --to=tomasz.figa@gmail.com \
    --cc=dianders@chromium.org \
    --cc=kgene.kim@samsung.com \
    --cc=linus.walleij@linaro.org \
    --cc=linux-kernel@vger.kernel.org \
    --cc=linux-samsung-soc@vger.kernel.org \
    --cc=olof@lixom.net \
    --cc=prathyush.k@samsung.com \
    --cc=swarren@wwwdotorg.org \
    --cc=thomas.abraham@linaro.org \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.