diff mbox series

[2/2] platform/chrome: cros_ec_spi: Boot fingerprint processor during probe

Message ID 20220314232214.4183078-3-swboyd@chromium.org (mailing list archive)
State Superseded
Headers show
Series Update cros-ec-spi for fingerprint devices | expand

Commit Message

Stephen Boyd March 14, 2022, 11:22 p.m. UTC
Add gpio control to this driver so that the fingerprint device can be
booted if the BIOS isn't doing it already. This eases bringup of new
hardware as we don't have to wait for the BIOS to be ready, supports
kexec where the GPIOs may not be configured by the previous boot stage,
and is all around good hygiene because we control GPIOs for this device
from the device driver.

Cc: Guenter Roeck <groeck@chromium.org>
Cc: Douglas Anderson <dianders@chromium.org>
Cc: Craig Hesling <hesling@chromium.org>
Cc: Tom Hughes <tomhughes@chromium.org>
Cc: Alexandru M Stan <amstan@chromium.org>
Signed-off-by: Stephen Boyd <swboyd@chromium.org>
---
 drivers/platform/chrome/cros_ec_spi.c | 38 ++++++++++++++++++++++++---
 1 file changed, 35 insertions(+), 3 deletions(-)

Comments

Alexandru M Stan March 15, 2022, 12:36 a.m. UTC | #1
On Mon, Mar 14, 2022 at 4:22 PM Stephen Boyd <swboyd@chromium.org> wrote:
>
> Add gpio control to this driver so that the fingerprint device can be
> booted if the BIOS isn't doing it already. This eases bringup of new
> hardware as we don't have to wait for the BIOS to be ready, supports
> kexec where the GPIOs may not be configured by the previous boot stage,
> and is all around good hygiene because we control GPIOs for this device
> from the device driver.
>
> Cc: Guenter Roeck <groeck@chromium.org>
> Cc: Douglas Anderson <dianders@chromium.org>
> Cc: Craig Hesling <hesling@chromium.org>
> Cc: Tom Hughes <tomhughes@chromium.org>
> Cc: Alexandru M Stan <amstan@chromium.org>
> Signed-off-by: Stephen Boyd <swboyd@chromium.org>
> ---
>  drivers/platform/chrome/cros_ec_spi.c | 38 ++++++++++++++++++++++++---
>  1 file changed, 35 insertions(+), 3 deletions(-)
>
> diff --git a/drivers/platform/chrome/cros_ec_spi.c b/drivers/platform/chrome/cros_ec_spi.c
> index 14c4046fa04d..77577650afce 100644
> --- a/drivers/platform/chrome/cros_ec_spi.c
> +++ b/drivers/platform/chrome/cros_ec_spi.c
> @@ -4,6 +4,7 @@
>  // Copyright (C) 2012 Google, Inc
>
>  #include <linux/delay.h>
> +#include <linux/gpio/consumer.h>
>  #include <linux/kernel.h>
>  #include <linux/module.h>
>  #include <linux/of.h>
> @@ -77,6 +78,8 @@ struct cros_ec_spi {
>         unsigned int start_of_msg_delay;
>         unsigned int end_of_msg_delay;
>         struct kthread_worker *high_pri_worker;
> +       struct gpio_desc *boot0;
> +       struct gpio_desc *reset;
>  };
>
>  typedef int (*cros_ec_xfer_fn_t) (struct cros_ec_device *ec_dev,
> @@ -690,7 +693,7 @@ static int cros_ec_cmd_xfer_spi(struct cros_ec_device *ec_dev,
>         return cros_ec_xfer_high_pri(ec_dev, ec_msg, do_cros_ec_cmd_xfer_spi);
>  }
>
> -static void cros_ec_spi_dt_probe(struct cros_ec_spi *ec_spi, struct device *dev)
> +static int cros_ec_spi_dt_probe(struct cros_ec_spi *ec_spi, struct device *dev)
>  {
>         struct device_node *np = dev->of_node;
>         u32 val;
> @@ -703,6 +706,31 @@ static void cros_ec_spi_dt_probe(struct cros_ec_spi *ec_spi, struct device *dev)
>         ret = of_property_read_u32(np, "google,cros-ec-spi-msg-delay", &val);
>         if (!ret)
>                 ec_spi->end_of_msg_delay = val;
> +
> +       if (!of_device_is_compatible(np, "google,cros-ec-fp"))
> +               return 0;
> +
> +       ec_spi->boot0 = devm_gpiod_get(dev, "boot0", 0);
> +       if (IS_ERR(ec_spi->boot0))
> +               return PTR_ERR(ec_spi->boot0);
> +
> +       ec_spi->reset = devm_gpiod_get(dev, "reset", 0);
> +       if (IS_ERR(ec_spi->reset))
> +               return PTR_ERR(ec_spi->reset);
> +
> +       /*
> +        * Take the FPMCU out of reset and wait for it to boot if it's in
> +        * bootloader mode or held in reset. Otherwise the BIOS has already
> +        * powered on the device earlier in boot in which case there's nothing
> +        * to do.
> +        */
> +       if (!gpiod_get_value(ec_spi->boot0) || gpiod_get_value(ec_spi->reset)) {
> +               gpiod_set_value(ec_spi->boot0, 1);
If you follow my suggestions on the other patch to keep boot0
active_high, this needs to change back to a 0. Since normal=0.

> +               gpiod_set_value(ec_spi->reset, 0);
> +               usleep_range(1000, 2000);
You have to be careful here. I assume by the end of this if you expect
the fpmcu to be in normal mode.

There is a corner case (which I guess is unlikely) that the fpmcu,
just before the if, was out of reset but in bootloader mode (boot0=1,
nrst=1, physically). At that point this code will enter the if body
and set boot0=0, but the reset signal would not be changed (we're
already out of reset!), so we'll still be in bootloader mode since
there was no resetting (so no sampling of boot0).

I suggest you change this if body to look like:

+               gpiod_set_value(ec_spi->boot0, 0); // normal mode
+               gpiod_set_value(ec_spi->reset, 1); // enter reset
(these reset comments might be optional)
+               usleep_range(1000, 2000);
+               gpiod_set_value(ec_spi->reset, 0); // release reset
+               usleep_range(1000, 2000); // maybe increase?

In the normal case (when we do have firmware support). All this code
will probably get skipped and there will be plenty of time to boot the
fpmcu before the driver enumerates.

Speaking of, have you tried this path? I think booting (fpmcu all the
way to RW) usually takes forever (seconds or so?). I remember Craig
mentioning this is why we want to do it so early (in ap firmware)
since by the time the kernel tries to enumerate it'll be done.

If that's the case you might want to change that last usleep_range to
actually wait that long.

> +       }
> +
> +       return 0;
>  }
>
>  static void cros_ec_spi_high_pri_release(void *worker)
> @@ -754,8 +782,10 @@ static int cros_ec_spi_probe(struct spi_device *spi)
>         if (!ec_dev)
>                 return -ENOMEM;
>
> -       /* Check for any DT properties */
> -       cros_ec_spi_dt_probe(ec_spi, dev);
> +       /* Check for any DT properties and boot fpmcu if applicable */
> +       err = cros_ec_spi_dt_probe(ec_spi, dev);
> +       if (err)
> +               return err;
>
>         spi_set_drvdata(spi, ec_dev);
>         ec_dev->dev = dev;
> @@ -813,12 +843,14 @@ static SIMPLE_DEV_PM_OPS(cros_ec_spi_pm_ops, cros_ec_spi_suspend,
>                          cros_ec_spi_resume);
>
>  static const struct of_device_id cros_ec_spi_of_match[] = {
> +       { .compatible = "google,cros-ec-fp", },
>         { .compatible = "google,cros-ec-spi", },
>         { /* sentinel */ },
>  };
>  MODULE_DEVICE_TABLE(of, cros_ec_spi_of_match);
>
>  static const struct spi_device_id cros_ec_spi_id[] = {
> +       { "cros-ec-fp", 0 },
>         { "cros-ec-spi", 0 },
>         { }
>  };
> --
> https://chromeos.dev
>

Alexandru Stan (amstan)
Stephen Boyd March 15, 2022, 4:16 p.m. UTC | #2
Quoting Alexandru M Stan (2022-03-14 17:36:47)
> On Mon, Mar 14, 2022 at 4:22 PM Stephen Boyd <swboyd@chromium.org> wrote:
> >
> > Add gpio control to this driver so that the fingerprint device can be
> > booted if the BIOS isn't doing it already. This eases bringup of new
> > hardware as we don't have to wait for the BIOS to be ready, supports
> > kexec where the GPIOs may not be configured by the previous boot stage,
> > and is all around good hygiene because we control GPIOs for this device
> > from the device driver.
> >
> > Cc: Guenter Roeck <groeck@chromium.org>
> > Cc: Douglas Anderson <dianders@chromium.org>
> > Cc: Craig Hesling <hesling@chromium.org>
> > Cc: Tom Hughes <tomhughes@chromium.org>
> > Cc: Alexandru M Stan <amstan@chromium.org>
> > Signed-off-by: Stephen Boyd <swboyd@chromium.org>
> > ---
> >  drivers/platform/chrome/cros_ec_spi.c | 38 ++++++++++++++++++++++++---
> >  1 file changed, 35 insertions(+), 3 deletions(-)
> >
> > diff --git a/drivers/platform/chrome/cros_ec_spi.c b/drivers/platform/chrome/cros_ec_spi.c
> > index 14c4046fa04d..77577650afce 100644
> > --- a/drivers/platform/chrome/cros_ec_spi.c
> > +++ b/drivers/platform/chrome/cros_ec_spi.c
> > @@ -4,6 +4,7 @@
> >  // Copyright (C) 2012 Google, Inc
> >
> >  #include <linux/delay.h>
> > +#include <linux/gpio/consumer.h>
> >  #include <linux/kernel.h>
> >  #include <linux/module.h>
> >  #include <linux/of.h>
> > @@ -77,6 +78,8 @@ struct cros_ec_spi {
> >         unsigned int start_of_msg_delay;
> >         unsigned int end_of_msg_delay;
> >         struct kthread_worker *high_pri_worker;
> > +       struct gpio_desc *boot0;
> > +       struct gpio_desc *reset;
> >  };
> >
> >  typedef int (*cros_ec_xfer_fn_t) (struct cros_ec_device *ec_dev,
> > @@ -690,7 +693,7 @@ static int cros_ec_cmd_xfer_spi(struct cros_ec_device *ec_dev,
> >         return cros_ec_xfer_high_pri(ec_dev, ec_msg, do_cros_ec_cmd_xfer_spi);
> >  }
> >
> > -static void cros_ec_spi_dt_probe(struct cros_ec_spi *ec_spi, struct device *dev)
> > +static int cros_ec_spi_dt_probe(struct cros_ec_spi *ec_spi, struct device *dev)
> >  {
> >         struct device_node *np = dev->of_node;
> >         u32 val;
> > @@ -703,6 +706,31 @@ static void cros_ec_spi_dt_probe(struct cros_ec_spi *ec_spi, struct device *dev)
> >         ret = of_property_read_u32(np, "google,cros-ec-spi-msg-delay", &val);
> >         if (!ret)
> >                 ec_spi->end_of_msg_delay = val;
> > +
> > +       if (!of_device_is_compatible(np, "google,cros-ec-fp"))
> > +               return 0;
> > +
> > +       ec_spi->boot0 = devm_gpiod_get(dev, "boot0", 0);
> > +       if (IS_ERR(ec_spi->boot0))
> > +               return PTR_ERR(ec_spi->boot0);
> > +
> > +       ec_spi->reset = devm_gpiod_get(dev, "reset", 0);
> > +       if (IS_ERR(ec_spi->reset))
> > +               return PTR_ERR(ec_spi->reset);
> > +
> > +       /*
> > +        * Take the FPMCU out of reset and wait for it to boot if it's in
> > +        * bootloader mode or held in reset. Otherwise the BIOS has already
> > +        * powered on the device earlier in boot in which case there's nothing
> > +        * to do.
> > +        */
> > +       if (!gpiod_get_value(ec_spi->boot0) || gpiod_get_value(ec_spi->reset)) {
> > +               gpiod_set_value(ec_spi->boot0, 1);
> If you follow my suggestions on the other patch to keep boot0
> active_high, this needs to change back to a 0. Since normal=0.
>
> > +               gpiod_set_value(ec_spi->reset, 0);
> > +               usleep_range(1000, 2000);
> You have to be careful here. I assume by the end of this if you expect
> the fpmcu to be in normal mode.
>
> There is a corner case (which I guess is unlikely) that the fpmcu,
> just before the if, was out of reset but in bootloader mode (boot0=1,
> nrst=1, physically). At that point this code will enter the if body
> and set boot0=0, but the reset signal would not be changed (we're
> already out of reset!), so we'll still be in bootloader mode since
> there was no resetting (so no sampling of boot0).
>
> I suggest you change this if body to look like:
>
> +               gpiod_set_value(ec_spi->boot0, 0); // normal mode
> +               gpiod_set_value(ec_spi->reset, 1); // enter reset
> (these reset comments might be optional)
> +               usleep_range(1000, 2000);
> +               gpiod_set_value(ec_spi->reset, 0); // release reset
> +               usleep_range(1000, 2000); // maybe increase?

Ok got it. I'll make the change and test it out.

>
> In the normal case (when we do have firmware support). All this code
> will probably get skipped and there will be plenty of time to boot the
> fpmcu before the driver enumerates.
>
> Speaking of, have you tried this path? I think booting (fpmcu all the
> way to RW) usually takes forever (seconds or so?). I remember Craig
> mentioning this is why we want to do it so early (in ap firmware)
> since by the time the kernel tries to enumerate it'll be done.
>
> If that's the case you might want to change that last usleep_range to
> actually wait that long.

I tested this by running the flashing code and then seeing if the cros
ec driver probed. I'll have to manually put it into bootloader mode and
then bind this driver to see how long it takes. Is there any way I can
poll an irq line or something to wait for boot? That would be much
better vs. sticking some delay into here.
diff mbox series

Patch

diff --git a/drivers/platform/chrome/cros_ec_spi.c b/drivers/platform/chrome/cros_ec_spi.c
index 14c4046fa04d..77577650afce 100644
--- a/drivers/platform/chrome/cros_ec_spi.c
+++ b/drivers/platform/chrome/cros_ec_spi.c
@@ -4,6 +4,7 @@ 
 // Copyright (C) 2012 Google, Inc
 
 #include <linux/delay.h>
+#include <linux/gpio/consumer.h>
 #include <linux/kernel.h>
 #include <linux/module.h>
 #include <linux/of.h>
@@ -77,6 +78,8 @@  struct cros_ec_spi {
 	unsigned int start_of_msg_delay;
 	unsigned int end_of_msg_delay;
 	struct kthread_worker *high_pri_worker;
+	struct gpio_desc *boot0;
+	struct gpio_desc *reset;
 };
 
 typedef int (*cros_ec_xfer_fn_t) (struct cros_ec_device *ec_dev,
@@ -690,7 +693,7 @@  static int cros_ec_cmd_xfer_spi(struct cros_ec_device *ec_dev,
 	return cros_ec_xfer_high_pri(ec_dev, ec_msg, do_cros_ec_cmd_xfer_spi);
 }
 
-static void cros_ec_spi_dt_probe(struct cros_ec_spi *ec_spi, struct device *dev)
+static int cros_ec_spi_dt_probe(struct cros_ec_spi *ec_spi, struct device *dev)
 {
 	struct device_node *np = dev->of_node;
 	u32 val;
@@ -703,6 +706,31 @@  static void cros_ec_spi_dt_probe(struct cros_ec_spi *ec_spi, struct device *dev)
 	ret = of_property_read_u32(np, "google,cros-ec-spi-msg-delay", &val);
 	if (!ret)
 		ec_spi->end_of_msg_delay = val;
+
+	if (!of_device_is_compatible(np, "google,cros-ec-fp"))
+		return 0;
+
+	ec_spi->boot0 = devm_gpiod_get(dev, "boot0", 0);
+	if (IS_ERR(ec_spi->boot0))
+		return PTR_ERR(ec_spi->boot0);
+
+	ec_spi->reset = devm_gpiod_get(dev, "reset", 0);
+	if (IS_ERR(ec_spi->reset))
+		return PTR_ERR(ec_spi->reset);
+
+	/*
+	 * Take the FPMCU out of reset and wait for it to boot if it's in
+	 * bootloader mode or held in reset. Otherwise the BIOS has already
+	 * powered on the device earlier in boot in which case there's nothing
+	 * to do.
+	 */
+	if (!gpiod_get_value(ec_spi->boot0) || gpiod_get_value(ec_spi->reset)) {
+		gpiod_set_value(ec_spi->boot0, 1);
+		gpiod_set_value(ec_spi->reset, 0);
+		usleep_range(1000, 2000);
+	}
+
+	return 0;
 }
 
 static void cros_ec_spi_high_pri_release(void *worker)
@@ -754,8 +782,10 @@  static int cros_ec_spi_probe(struct spi_device *spi)
 	if (!ec_dev)
 		return -ENOMEM;
 
-	/* Check for any DT properties */
-	cros_ec_spi_dt_probe(ec_spi, dev);
+	/* Check for any DT properties and boot fpmcu if applicable */
+	err = cros_ec_spi_dt_probe(ec_spi, dev);
+	if (err)
+		return err;
 
 	spi_set_drvdata(spi, ec_dev);
 	ec_dev->dev = dev;
@@ -813,12 +843,14 @@  static SIMPLE_DEV_PM_OPS(cros_ec_spi_pm_ops, cros_ec_spi_suspend,
 			 cros_ec_spi_resume);
 
 static const struct of_device_id cros_ec_spi_of_match[] = {
+	{ .compatible = "google,cros-ec-fp", },
 	{ .compatible = "google,cros-ec-spi", },
 	{ /* sentinel */ },
 };
 MODULE_DEVICE_TABLE(of, cros_ec_spi_of_match);
 
 static const struct spi_device_id cros_ec_spi_id[] = {
+	{ "cros-ec-fp", 0 },
 	{ "cros-ec-spi", 0 },
 	{ }
 };