From 2ba894cc02170c8e98aebe3235f05d26f7046ad3 Mon Sep 17 00:00:00 2001 From: Nick Hollinghurst Date: Wed, 1 Mar 2023 17:57:11 +0000 Subject: [PATCH 374/697] spi: gpio: Fix spi-gpio to correctly implement sck-idle-input 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 --- drivers/spi/spi-gpio.c | 69 +++++++++++++++++++++++++++++------------- 1 file changed, 48 insertions(+), 21 deletions(-) --- a/drivers/spi/spi-gpio.c +++ b/drivers/spi/spi-gpio.c @@ -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 s 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) @@ -261,11 +268,14 @@ static int spi_gpio_setup(struct spi_dev /* * 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) { - ret = gpiod_direction_output(cs, !(spi->mode & SPI_CS_HIGH)); + ret = gpiod_direction_output(cs, + !((spi->mode & SPI_CS_HIGH) || + spi_gpio->cs_dont_invert)); if (ret) return ret; } @@ -342,13 +352,43 @@ static int spi_gpio_request(struct devic 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_controller *master, + bool gpio_defines_polarity) +{ + int i; + struct spi_gpio *spi_gpio = spi_controller_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; +} + static int spi_gpio_probe_pdata(struct platform_device *pdev, struct spi_controller *host) { 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) @@ -360,20 +400,7 @@ static int spi_gpio_probe_pdata(struct p */ 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)