diff options
-rw-r--r-- | drivers/spi/spi.c | 103 |
1 files changed, 72 insertions, 31 deletions
diff --git a/drivers/spi/spi.c b/drivers/spi/spi.c index bced6876de79..498f9b7419a4 100644 --- a/drivers/spi/spi.c +++ b/drivers/spi/spi.c @@ -1852,9 +1852,18 @@ static void of_register_spi_devices(struct spi_controller *ctlr) { } #endif #ifdef CONFIG_ACPI -static void acpi_spi_parse_apple_properties(struct spi_device *spi) +struct acpi_spi_lookup { + struct spi_controller *ctlr; + u32 max_speed_hz; + u32 mode; + int irq; + u8 bits_per_word; + u8 chip_select; +}; + +static void acpi_spi_parse_apple_properties(struct acpi_device *dev, + struct acpi_spi_lookup *lookup) { - struct acpi_device *dev = ACPI_COMPANION(&spi->dev); const union acpi_object *obj; if (!x86_apple_machine) @@ -1862,35 +1871,46 @@ static void acpi_spi_parse_apple_properties(struct spi_device *spi) if (!acpi_dev_get_property(dev, "spiSclkPeriod", ACPI_TYPE_BUFFER, &obj) && obj->buffer.length >= 4) - spi->max_speed_hz = NSEC_PER_SEC / *(u32 *)obj->buffer.pointer; + lookup->max_speed_hz = NSEC_PER_SEC / *(u32 *)obj->buffer.pointer; if (!acpi_dev_get_property(dev, "spiWordSize", ACPI_TYPE_BUFFER, &obj) && obj->buffer.length == 8) - spi->bits_per_word = *(u64 *)obj->buffer.pointer; + lookup->bits_per_word = *(u64 *)obj->buffer.pointer; if (!acpi_dev_get_property(dev, "spiBitOrder", ACPI_TYPE_BUFFER, &obj) && obj->buffer.length == 8 && !*(u64 *)obj->buffer.pointer) - spi->mode |= SPI_LSB_FIRST; + lookup->mode |= SPI_LSB_FIRST; if (!acpi_dev_get_property(dev, "spiSPO", ACPI_TYPE_BUFFER, &obj) && obj->buffer.length == 8 && *(u64 *)obj->buffer.pointer) - spi->mode |= SPI_CPOL; + lookup->mode |= SPI_CPOL; if (!acpi_dev_get_property(dev, "spiSPH", ACPI_TYPE_BUFFER, &obj) && obj->buffer.length == 8 && *(u64 *)obj->buffer.pointer) - spi->mode |= SPI_CPHA; + lookup->mode |= SPI_CPHA; } static int acpi_spi_add_resource(struct acpi_resource *ares, void *data) { - struct spi_device *spi = data; - struct spi_controller *ctlr = spi->controller; + struct acpi_spi_lookup *lookup = data; + struct spi_controller *ctlr = lookup->ctlr; if (ares->type == ACPI_RESOURCE_TYPE_SERIAL_BUS) { struct acpi_resource_spi_serialbus *sb; + acpi_handle parent_handle; + acpi_status status; sb = &ares->data.spi_serial_bus; if (sb->type == ACPI_RESOURCE_SERIAL_TYPE_SPI) { + + status = acpi_get_handle(NULL, + sb->resource_source.string_ptr, + &parent_handle); + + if (!status || + ACPI_HANDLE(ctlr->dev.parent) != parent_handle) + return -ENODEV; + /* * ACPI DeviceSelection numbering is handled by the * host controller driver in Windows and can vary @@ -1903,25 +1923,25 @@ static int acpi_spi_add_resource(struct acpi_resource *ares, void *data) sb->device_selection); if (cs < 0) return cs; - spi->chip_select = cs; + lookup->chip_select = cs; } else { - spi->chip_select = sb->device_selection; + lookup->chip_select = sb->device_selection; } - spi->max_speed_hz = sb->connection_speed; + lookup->max_speed_hz = sb->connection_speed; if (sb->clock_phase == ACPI_SPI_SECOND_PHASE) - spi->mode |= SPI_CPHA; + lookup->mode |= SPI_CPHA; if (sb->clock_polarity == ACPI_SPI_START_HIGH) - spi->mode |= SPI_CPOL; + lookup->mode |= SPI_CPOL; if (sb->device_polarity == ACPI_SPI_ACTIVE_HIGH) - spi->mode |= SPI_CS_HIGH; + lookup->mode |= SPI_CS_HIGH; } - } else if (spi->irq < 0) { + } else if (lookup->irq < 0) { struct resource r; if (acpi_dev_resource_interrupt(ares, 0, &r)) - spi->irq = r.start; + lookup->irq = r.start; } /* Always tell the ACPI core to skip this resource */ @@ -1931,7 +1951,9 @@ static int acpi_spi_add_resource(struct acpi_resource *ares, void *data) static acpi_status acpi_register_spi_device(struct spi_controller *ctlr, struct acpi_device *adev) { + acpi_handle parent_handle = NULL; struct list_head resource_list; + struct acpi_spi_lookup lookup; struct spi_device *spi; int ret; @@ -1939,28 +1961,44 @@ static acpi_status acpi_register_spi_device(struct spi_controller *ctlr, acpi_device_enumerated(adev)) return AE_OK; - spi = spi_alloc_device(ctlr); - if (!spi) { - dev_err(&ctlr->dev, "failed to allocate SPI device for %s\n", - dev_name(&adev->dev)); - return AE_NO_MEMORY; - } - - ACPI_COMPANION_SET(&spi->dev, adev); - spi->irq = -1; + lookup.ctlr = ctlr; + lookup.mode = 0; + lookup.bits_per_word = 0; + lookup.irq = -1; INIT_LIST_HEAD(&resource_list); ret = acpi_dev_get_resources(adev, &resource_list, - acpi_spi_add_resource, spi); + acpi_spi_add_resource, &lookup); acpi_dev_free_resource_list(&resource_list); - acpi_spi_parse_apple_properties(spi); + if (ret < 0) + /* found SPI in _CRS but it points to another controller */ + return AE_OK; - if (ret < 0 || !spi->max_speed_hz) { - spi_dev_put(spi); + if (!lookup.max_speed_hz && + !ACPI_FAILURE(acpi_get_parent(adev->handle, &parent_handle)) && + ACPI_HANDLE(ctlr->dev.parent) == parent_handle) { + /* Apple does not use _CRS but nested devices for SPI slaves */ + acpi_spi_parse_apple_properties(adev, &lookup); + } + + if (!lookup.max_speed_hz) return AE_OK; + + spi = spi_alloc_device(ctlr); + if (!spi) { + dev_err(&ctlr->dev, "failed to allocate SPI device for %s\n", + dev_name(&adev->dev)); + return AE_NO_MEMORY; } + ACPI_COMPANION_SET(&spi->dev, adev); + spi->max_speed_hz = lookup.max_speed_hz; + spi->mode = lookup.mode; + spi->irq = lookup.irq; + spi->bits_per_word = lookup.bits_per_word; + spi->chip_select = lookup.chip_select; + acpi_set_modalias(adev, acpi_device_hid(adev), spi->modalias, sizeof(spi->modalias)); @@ -1992,6 +2030,8 @@ static acpi_status acpi_spi_add_device(acpi_handle handle, u32 level, return acpi_register_spi_device(ctlr, adev); } +#define SPI_ACPI_ENUMERATE_MAX_DEPTH 32 + static void acpi_register_spi_devices(struct spi_controller *ctlr) { acpi_status status; @@ -2001,7 +2041,8 @@ static void acpi_register_spi_devices(struct spi_controller *ctlr) if (!handle) return; - status = acpi_walk_namespace(ACPI_TYPE_DEVICE, handle, 1, + status = acpi_walk_namespace(ACPI_TYPE_DEVICE, ACPI_ROOT_OBJECT, + SPI_ACPI_ENUMERATE_MAX_DEPTH, acpi_spi_add_device, NULL, ctlr, NULL); if (ACPI_FAILURE(status)) dev_warn(&ctlr->dev, "failed to enumerate SPI slaves\n"); |