mirror of
				https://github.com/Ysurac/openmptcprouter.git
				synced 2025-03-09 15:40:20 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			150 lines
		
	
	
	
		
			4.8 KiB
		
	
	
	
		
			Diff
		
	
	
	
	
	
			
		
		
	
	
			150 lines
		
	
	
	
		
			4.8 KiB
		
	
	
	
		
			Diff
		
	
	
	
	
	
| From fc7ca7f1cd650398f2ca42b5fb693bc977fb5ec2 Mon Sep 17 00:00:00 2001
 | |
| From: Nick Hollinghurst <nick.hollinghurst@raspberrypi.com>
 | |
| Date: Wed, 1 Mar 2023 17:57:11 +0000
 | |
| Subject: [PATCH] drivers: spi: 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 <nick.hollinghurst@raspberrypi.com>
 | |
| ---
 | |
|  drivers/spi/spi-gpio.c | 74 +++++++++++++++++++++++++++++-------------
 | |
|  1 file changed, 51 insertions(+), 23 deletions(-)
 | |
| 
 | |
| --- a/drivers/spi/spi-gpio.c
 | |
| +++ b/drivers/spi/spi-gpio.c
 | |
| @@ -37,6 +37,7 @@ struct spi_gpio {
 | |
|  	struct gpio_desc		*mosi;
 | |
|  	bool				sck_idle_input;
 | |
|  	struct gpio_desc		**cs_gpios;
 | |
| +	bool                            cs_dont_invert;
 | |
|  };
 | |
|  
 | |
|  /*----------------------------------------------------------------------*/
 | |
| @@ -233,12 +234,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->chip_select];
 | |
| +		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)
 | |
| @@ -254,12 +261,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->chip_select];
 | |
|  		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)
 | |
| @@ -336,6 +345,38 @@ 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_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" },
 | |
| @@ -346,10 +387,12 @@ MODULE_DEVICE_TABLE(of, spi_gpio_dt_ids)
 | |
|  static int spi_gpio_probe_dt(struct platform_device *pdev,
 | |
|  			     struct spi_master *master)
 | |
|  {
 | |
| -	master->dev.of_node = pdev->dev.of_node;
 | |
| -	master->use_gpio_descriptors = true;
 | |
| +	struct device *dev = &pdev->dev;
 | |
|  
 | |
| -	return 0;
 | |
| +	master->dev.of_node = dev->of_node;
 | |
| +	master->num_chipselect = gpiod_count(dev, "cs");
 | |
| +
 | |
| +	return spi_gpio_probe_get_cs_gpios(dev, master, true);
 | |
|  }
 | |
|  #else
 | |
|  static inline int spi_gpio_probe_dt(struct platform_device *pdev,
 | |
| @@ -364,8 +407,6 @@ static int spi_gpio_probe_pdata(struct p
 | |
|  {
 | |
|  	struct device *dev = &pdev->dev;
 | |
|  	struct spi_gpio_platform_data *pdata = dev_get_platdata(dev);
 | |
| -	struct spi_gpio *spi_gpio = spi_master_get_devdata(master);
 | |
| -	int i;
 | |
|  
 | |
|  #ifdef GENERIC_BITBANG
 | |
|  	if (!pdata || !pdata->num_chipselect)
 | |
| @@ -377,20 +418,7 @@ static int spi_gpio_probe_pdata(struct p
 | |
|  	 */
 | |
|  	master->num_chipselect = pdata->num_chipselect ?: 1;
 | |
|  
 | |
| -	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,
 | |
| -							     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, master, false);
 | |
|  }
 | |
|  
 | |
|  static int spi_gpio_probe(struct platform_device *pdev)
 |