spi: gpio: Fix spi-gpio to correctly implement sck-idle-input
authorNick Hollinghurst <nick.hollinghurst@raspberrypi.com>
Wed, 1 Mar 2023 17:57:11 +0000 (17:57 +0000)
committerDom Cobley <popcornmix@gmail.com>
Mon, 19 Feb 2024 11:34:50 +0000 (11:34 +0000)
Formerly, if configured using DT, CS GPIOs were driven from spi.c
and it was possible for CS to be asserted (low) *before* starting
to drive SCK. CS GPIOs have been brought under control of this
driver in both ACPI and DT cases, with a fixup for GPIO polarity.

Signed-off-by: Nick Hollinghurst <nick.hollinghurst@raspberrypi.com>
drivers/spi/spi-gpio.c

index af9cef6..302e640 100644 (file)
@@ -34,8 +34,9 @@ struct spi_gpio {
        struct gpio_desc                *sck;
        struct gpio_desc                *miso;
        struct gpio_desc                *mosi;
-       bool                            sck_idle_input;
        struct gpio_desc                **cs_gpios;
+       bool                            sck_idle_input;
+       bool                            cs_dont_invert;
 };
 
 /*----------------------------------------------------------------------*/
@@ -232,12 +233,18 @@ static void spi_gpio_chipselect(struct spi_device *spi, int is_active)
                        gpiod_set_value_cansleep(spi_gpio->sck, spi->mode & SPI_CPOL);
        }
 
-       /* Drive chip select line, if we have one */
+       /*
+        * Drive chip select line, if we have one.
+        * SPI chip selects are normally active-low, but when
+        * cs_dont_invert is set, we assume their polarity is
+        * controlled by the GPIO, and write '1' to assert.
+        */
        if (spi_gpio->cs_gpios) {
                struct gpio_desc *cs = spi_gpio->cs_gpios[spi_get_chipselect(spi, 0)];
+               int val = ((spi->mode & SPI_CS_HIGH) || spi_gpio->cs_dont_invert) ?
+                       is_active : !is_active;
 
-               /* SPI chip selects are normally active-low */
-               gpiod_set_value_cansleep(cs, (spi->mode & SPI_CS_HIGH) ? is_active : !is_active);
+               gpiod_set_value_cansleep(cs, val);
        }
 
        if (spi_gpio->sck_idle_input && !is_active)
@@ -253,12 +260,14 @@ static int spi_gpio_setup(struct spi_device *spi)
        /*
         * The CS GPIOs have already been
         * initialized from the descriptor lookup.
+        * Here we set them to the non-asserted state.
         */
        if (spi_gpio->cs_gpios) {
                cs = spi_gpio->cs_gpios[spi_get_chipselect(spi, 0)];
                if (!spi->controller_state && cs)
                        status = gpiod_direction_output(cs,
-                                                 !(spi->mode & SPI_CS_HIGH));
+                                                       !((spi->mode & SPI_CS_HIGH) ||
+                                                          spi_gpio->cs_dont_invert));
        }
 
        if (!status)
@@ -335,6 +344,38 @@ static int spi_gpio_request(struct device *dev, struct spi_gpio *spi_gpio)
        return PTR_ERR_OR_ZERO(spi_gpio->sck);
 }
 
+/*
+ * In order to implement "sck-idle-input" (which requires SCK
+ * direction and CS level to be switched in a particular order),
+ * we need to control GPIO chip selects from within this driver.
+ */
+
+static int spi_gpio_probe_get_cs_gpios(struct device *dev,
+                                      struct spi_master *master,
+                                      bool gpio_defines_polarity)
+{
+       int i;
+       struct spi_gpio *spi_gpio = spi_master_get_devdata(master);
+
+       spi_gpio->cs_dont_invert = gpio_defines_polarity;
+       spi_gpio->cs_gpios = devm_kcalloc(dev, master->num_chipselect,
+                                         sizeof(*spi_gpio->cs_gpios),
+                                         GFP_KERNEL);
+       if (!spi_gpio->cs_gpios)
+               return -ENOMEM;
+
+       for (i = 0; i < master->num_chipselect; i++) {
+               spi_gpio->cs_gpios[i] =
+                       devm_gpiod_get_index(dev, "cs", i,
+                                            gpio_defines_polarity ?
+                                               GPIOD_OUT_LOW : GPIOD_OUT_HIGH);
+               if (IS_ERR(spi_gpio->cs_gpios[i]))
+                       return PTR_ERR(spi_gpio->cs_gpios[i]);
+       }
+
+       return 0;
+}
+
 #ifdef CONFIG_OF
 static const struct of_device_id spi_gpio_dt_ids[] = {
        { .compatible = "spi-gpio" },
@@ -345,10 +386,12 @@ MODULE_DEVICE_TABLE(of, spi_gpio_dt_ids);
 static int spi_gpio_probe_dt(struct platform_device *pdev,
                             struct spi_controller *host)
 {
-       host->dev.of_node = pdev->dev.of_node;
-       host->use_gpio_descriptors = true;
+       struct device *dev = &pdev->dev;
 
-       return 0;
+       host->dev.of_node = dev->of_node;
+       host->num_chipselect = gpiod_count(dev, "cs");
+
+       return spi_gpio_probe_get_cs_gpios(dev, host, true);
 }
 #else
 static inline int spi_gpio_probe_dt(struct platform_device *pdev,
@@ -363,8 +406,6 @@ static int spi_gpio_probe_pdata(struct platform_device *pdev,
 {
        struct device *dev = &pdev->dev;
        struct spi_gpio_platform_data *pdata = dev_get_platdata(dev);
-       struct spi_gpio *spi_gpio = spi_controller_get_devdata(host);
-       int i;
 
 #ifdef GENERIC_BITBANG
        if (!pdata || !pdata->num_chipselect)
@@ -376,20 +417,7 @@ static int spi_gpio_probe_pdata(struct platform_device *pdev,
         */
        host->num_chipselect = pdata->num_chipselect ?: 1;
 
-       spi_gpio->cs_gpios = devm_kcalloc(dev, host->num_chipselect,
-                                         sizeof(*spi_gpio->cs_gpios),
-                                         GFP_KERNEL);
-       if (!spi_gpio->cs_gpios)
-               return -ENOMEM;
-
-       for (i = 0; i < host->num_chipselect; i++) {
-               spi_gpio->cs_gpios[i] = devm_gpiod_get_index(dev, "cs", i,
-                                                            GPIOD_OUT_HIGH);
-               if (IS_ERR(spi_gpio->cs_gpios[i]))
-                       return PTR_ERR(spi_gpio->cs_gpios[i]);
-       }
-
-       return 0;
+       return spi_gpio_probe_get_cs_gpios(dev, host, false);
 }
 
 static int spi_gpio_probe(struct platform_device *pdev)