Commit 4c3c5954 authored by Ard Biesheuvel's avatar Ard Biesheuvel Committed by Mark Brown

spi/acpi: enumerate all SPI slaves in the namespace

Currently, the ACPI enumeration that takes place when registering a
SPI master only considers immediate child devices in the ACPI namespace,
rather than checking the ResourceSource field in the SpiSerialBus()
resource descriptor.

This is incorrect: SPI slaves could reside anywhere in the ACPI
namespace, and so we should enumerate the entire namespace and look for
any device that refers to the newly registered SPI master in its
resource descriptor.

So refactor the existing code and use a lookup structure so that
allocating the SPI device structure is deferred until we have identified
the device as an actual child of the controller. This approach is
loosely based on the way the I2C subsystem handles ACPI enumeration.

Note that Apple x86 hardware does not rely on SpiSerialBus() resources
in _CRS but uses nested devices below the controller's device node in
the ACPI namespace, with a special set of device properties. This means
we have to take care to only parse those properties for device nodes
that are direct children of the controller node.

Cc: Mika Westerberg <mika.westerberg@linux.intel.com>
Cc: linux-spi@vger.kernel.org
Cc: broonie@kernel.org
Cc: andy.shevchenko@gmail.com
Cc: masahisa.kojima@linaro.org
Cc: "Rafael J. Wysocki" <rjw@rjwysocki.net>
Cc: Jarkko Nikula <jarkko.nikula@linux.intel.com>
Cc: linux-acpi@vger.kernel.org
Cc: Lukas Wunner <lukas@wunner.de>
Signed-off-by: default avatarArd Biesheuvel <ard.biesheuvel@linaro.org>
Signed-off-by: default avatarMark Brown <broonie@kernel.org>
parent 4343f611
...@@ -1852,9 +1852,18 @@ static void of_register_spi_devices(struct spi_controller *ctlr) { } ...@@ -1852,9 +1852,18 @@ static void of_register_spi_devices(struct spi_controller *ctlr) { }
#endif #endif
#ifdef CONFIG_ACPI #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; const union acpi_object *obj;
if (!x86_apple_machine) if (!x86_apple_machine)
...@@ -1862,35 +1871,46 @@ static void acpi_spi_parse_apple_properties(struct spi_device *spi) ...@@ -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) if (!acpi_dev_get_property(dev, "spiSclkPeriod", ACPI_TYPE_BUFFER, &obj)
&& obj->buffer.length >= 4) && 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) if (!acpi_dev_get_property(dev, "spiWordSize", ACPI_TYPE_BUFFER, &obj)
&& obj->buffer.length == 8) && 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) if (!acpi_dev_get_property(dev, "spiBitOrder", ACPI_TYPE_BUFFER, &obj)
&& obj->buffer.length == 8 && !*(u64 *)obj->buffer.pointer) && 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) if (!acpi_dev_get_property(dev, "spiSPO", ACPI_TYPE_BUFFER, &obj)
&& obj->buffer.length == 8 && *(u64 *)obj->buffer.pointer) && 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) if (!acpi_dev_get_property(dev, "spiSPH", ACPI_TYPE_BUFFER, &obj)
&& obj->buffer.length == 8 && *(u64 *)obj->buffer.pointer) && 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) static int acpi_spi_add_resource(struct acpi_resource *ares, void *data)
{ {
struct spi_device *spi = data; struct acpi_spi_lookup *lookup = data;
struct spi_controller *ctlr = spi->controller; struct spi_controller *ctlr = lookup->ctlr;
if (ares->type == ACPI_RESOURCE_TYPE_SERIAL_BUS) { if (ares->type == ACPI_RESOURCE_TYPE_SERIAL_BUS) {
struct acpi_resource_spi_serialbus *sb; struct acpi_resource_spi_serialbus *sb;
acpi_handle parent_handle;
acpi_status status;
sb = &ares->data.spi_serial_bus; sb = &ares->data.spi_serial_bus;
if (sb->type == ACPI_RESOURCE_SERIAL_TYPE_SPI) { 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 * ACPI DeviceSelection numbering is handled by the
* host controller driver in Windows and can vary * host controller driver in Windows and can vary
...@@ -1903,25 +1923,25 @@ static int acpi_spi_add_resource(struct acpi_resource *ares, void *data) ...@@ -1903,25 +1923,25 @@ static int acpi_spi_add_resource(struct acpi_resource *ares, void *data)
sb->device_selection); sb->device_selection);
if (cs < 0) if (cs < 0)
return cs; return cs;
spi->chip_select = cs; lookup->chip_select = cs;
} else { } 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) if (sb->clock_phase == ACPI_SPI_SECOND_PHASE)
spi->mode |= SPI_CPHA; lookup->mode |= SPI_CPHA;
if (sb->clock_polarity == ACPI_SPI_START_HIGH) if (sb->clock_polarity == ACPI_SPI_START_HIGH)
spi->mode |= SPI_CPOL; lookup->mode |= SPI_CPOL;
if (sb->device_polarity == ACPI_SPI_ACTIVE_HIGH) 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; struct resource r;
if (acpi_dev_resource_interrupt(ares, 0, &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 */ /* 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) ...@@ -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, static acpi_status acpi_register_spi_device(struct spi_controller *ctlr,
struct acpi_device *adev) struct acpi_device *adev)
{ {
acpi_handle parent_handle = NULL;
struct list_head resource_list; struct list_head resource_list;
struct acpi_spi_lookup lookup;
struct spi_device *spi; struct spi_device *spi;
int ret; int ret;
...@@ -1939,28 +1961,44 @@ static acpi_status acpi_register_spi_device(struct spi_controller *ctlr, ...@@ -1939,28 +1961,44 @@ static acpi_status acpi_register_spi_device(struct spi_controller *ctlr,
acpi_device_enumerated(adev)) acpi_device_enumerated(adev))
return AE_OK; return AE_OK;
spi = spi_alloc_device(ctlr); lookup.ctlr = ctlr;
if (!spi) { lookup.mode = 0;
dev_err(&ctlr->dev, "failed to allocate SPI device for %s\n", lookup.bits_per_word = 0;
dev_name(&adev->dev)); lookup.irq = -1;
return AE_NO_MEMORY;
}
ACPI_COMPANION_SET(&spi->dev, adev);
spi->irq = -1;
INIT_LIST_HEAD(&resource_list); INIT_LIST_HEAD(&resource_list);
ret = acpi_dev_get_resources(adev, &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_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) { if (!lookup.max_speed_hz &&
spi_dev_put(spi); !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; 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, acpi_set_modalias(adev, acpi_device_hid(adev), spi->modalias,
sizeof(spi->modalias)); sizeof(spi->modalias));
...@@ -1992,6 +2030,8 @@ static acpi_status acpi_spi_add_device(acpi_handle handle, u32 level, ...@@ -1992,6 +2030,8 @@ static acpi_status acpi_spi_add_device(acpi_handle handle, u32 level,
return acpi_register_spi_device(ctlr, adev); return acpi_register_spi_device(ctlr, adev);
} }
#define SPI_ACPI_ENUMERATE_MAX_DEPTH 32
static void acpi_register_spi_devices(struct spi_controller *ctlr) static void acpi_register_spi_devices(struct spi_controller *ctlr)
{ {
acpi_status status; acpi_status status;
...@@ -2001,7 +2041,8 @@ static void acpi_register_spi_devices(struct spi_controller *ctlr) ...@@ -2001,7 +2041,8 @@ static void acpi_register_spi_devices(struct spi_controller *ctlr)
if (!handle) if (!handle)
return; 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); acpi_spi_add_device, NULL, ctlr, NULL);
if (ACPI_FAILURE(status)) if (ACPI_FAILURE(status))
dev_warn(&ctlr->dev, "failed to enumerate SPI slaves\n"); dev_warn(&ctlr->dev, "failed to enumerate SPI slaves\n");
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment