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 <nick.hollinghurst@raspberrypi.com>
This commit is contained in:
Nick Hollinghurst
2023-03-01 17:57:11 +00:00
committed by Dom Cobley
parent 2e4a487e69
commit e63bd95c0a

View 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;
};
/*----------------------------------------------------------------------*/
@@ -204,12 +205,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)
@@ -233,11 +240,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) {
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;
}
@@ -314,31 +324,31 @@ static int spi_gpio_request(struct device *dev, struct spi_gpio *spi_gpio)
return PTR_ERR_OR_ZERO(spi_gpio->sck);
}
static int spi_gpio_probe_pdata(struct platform_device *pdev,
struct spi_controller *host)
/*
* 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)
{
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;
struct spi_gpio *spi_gpio = spi_controller_get_devdata(master);
if (!pdata)
return -ENODEV;
/* It's just one always-selected device, fine to continue */
if (!pdata->num_chipselect)
return 0;
host->num_chipselect = pdata->num_chipselect;
spi_gpio->cs_gpios = devm_kcalloc(dev, host->num_chipselect,
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 < host->num_chipselect; i++) {
spi_gpio->cs_gpios[i] = devm_gpiod_get_index(dev, "cs", i,
GPIOD_OUT_HIGH);
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]);
}
@@ -346,6 +356,24 @@ static int spi_gpio_probe_pdata(struct platform_device *pdev,
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);
if (!pdata)
return -ENODEV;
/*
* The host needs to think there is a chipselect even if not
* connected
*/
host->num_chipselect = pdata->num_chipselect ?: 1;
return spi_gpio_probe_get_cs_gpios(dev, host, false);
}
static int spi_gpio_probe(struct platform_device *pdev)
{
int status;