diff mbox

[U-Boot,v4,05/29] dm: spi: Add a uclass for SPI

Message ID 1413265336-9571-6-git-send-email-sjg@chromium.org
State Awaiting Upstream
Delegated to: Simon Glass
Headers show

Commit Message

Simon Glass Oct. 14, 2014, 5:41 a.m. UTC
Add a uclass which provides access to SPI buses and includes operations
required by SPI.

For a time driver model will need to co-exist with the legacy SPI interface
so some parts of the header file are changed depending on which is in use.
The exports are adjusted also since some functions are not available with
driver model.

Boards must define CONFIG_DM_SPI to use driver model for SPI.

Signed-off-by: Simon Glass <sjg@chromium.org>
Reviewed-by: Jagannadha Sutradharudu Teki <jagannadh.teki@gmail.com>
(Discussed some follow-up comments which will address in future add-ons)

---

Changes in v4: None
Changes in v3:
- Add a cs_info() method to the driver model SPI API
- Add a uclass for a generic SPI device (for use with the 'sspi' command)
- Add missing comments to spi.h
- Correct typo where 'slave' should say 'bus'
- Fix two comment typos
- Put the cs member back into spi_slave
- Use an explicit chip select value instead of reusing device sequence number

Changes in v2:
- Add missing comments for struct spi_slave
- Fix code nits from Daniel Schwierzeck
- Use 'bus' instead of 'dev' to make the API clearer

 common/exports.c         |   4 +-
 drivers/spi/Makefile     |   4 +
 drivers/spi/spi-uclass.c | 390 +++++++++++++++++++++++++++++++++++++++++++++++
 include/dm/uclass-id.h   |   2 +
 include/spi.h            | 254 +++++++++++++++++++++++++++++-
 5 files changed, 650 insertions(+), 4 deletions(-)
 create mode 100644 drivers/spi/spi-uclass.c

Comments

Simon Glass Oct. 23, 2014, 3:03 a.m. UTC | #1
On 13 October 2014 23:41, Simon Glass <sjg@chromium.org> wrote:
> Add a uclass which provides access to SPI buses and includes operations
> required by SPI.
>
> For a time driver model will need to co-exist with the legacy SPI interface
> so some parts of the header file are changed depending on which is in use.
> The exports are adjusted also since some functions are not available with
> driver model.
>
> Boards must define CONFIG_DM_SPI to use driver model for SPI.
>
> Signed-off-by: Simon Glass <sjg@chromium.org>
> Reviewed-by: Jagannadha Sutradharudu Teki <jagannadh.teki@gmail.com>
> (Discussed some follow-up comments which will address in future add-ons)
>
> ---
>
> Changes in v4: None
> Changes in v3:
> - Add a cs_info() method to the driver model SPI API
> - Add a uclass for a generic SPI device (for use with the 'sspi' command)
> - Add missing comments to spi.h
> - Correct typo where 'slave' should say 'bus'
> - Fix two comment typos
> - Put the cs member back into spi_slave
> - Use an explicit chip select value instead of reusing device sequence number
>
> Changes in v2:
> - Add missing comments for struct spi_slave
> - Fix code nits from Daniel Schwierzeck
> - Use 'bus' instead of 'dev' to make the API clearer
>
>  common/exports.c         |   4 +-
>  drivers/spi/Makefile     |   4 +
>  drivers/spi/spi-uclass.c | 390 +++++++++++++++++++++++++++++++++++++++++++++++
>  include/dm/uclass-id.h   |   2 +
>  include/spi.h            | 254 +++++++++++++++++++++++++++++-
>  5 files changed, 650 insertions(+), 4 deletions(-)
>  create mode 100644 drivers/spi/spi-uclass.c
>

Applied to u-boot-dm/master.
Stefan Roese Oct. 29, 2014, 1:02 p.m. UTC | #2
Hi Simon!

On 14.10.2014 07:41, Simon Glass wrote:
> Add a uclass which provides access to SPI buses and includes operations
> required by SPI.
>
> For a time driver model will need to co-exist with the legacy SPI interface
> so some parts of the header file are changed depending on which is in use.
> The exports are adjusted also since some functions are not available with
> driver model.
>
> Boards must define CONFIG_DM_SPI to use driver model for SPI.

I'm starting to use DM now. Finally! ;)

In my case I'm implementing the Designware master SPI driver for the 
SoCFPGA. And since its a new driver it really makes sense to use DM 
directly. I'm starting to get an overview now. One thing though that I 
noticed is, that the SPI DM implementation seems to be a bit FDT 
centric. Please see below.

<snip>

 > diff --git a/drivers/spi/spi-uclass.c b/drivers/spi/spi-uclass.c

<snip>

> +int spi_post_bind(struct udevice *dev)
> +{
> +	/* Scan the bus for devices */
> +	return dm_scan_fdt_node(dev, gd->fdt_blob, dev->of_offset, false);
> +}

SoCFPGA currently does not support CONFIG_OF_CONTROL (no dtb supplied). 
So I'm planning to use platform_data to instantiate the device. 
Something like this in the board file:

static const struct dw_spi_platdata spi_platdata = {
	.base = SOCFPGA_SPIM0_ADDRESS,
};

U_BOOT_DEVICE(dw_spi) = {
	.name = "dw_spi",
	.platdata = &spi_platdata,
};

What would the non-fdt method have to be to bind the device in 
spi_post_bind() above?

Thanks,
Stefan
Jagan Teki Oct. 29, 2014, 3:39 p.m. UTC | #3
Hi Stefan,

On 29 October 2014 18:32, Stefan Roese <sr@denx.de> wrote:
> Hi Simon!
>
> On 14.10.2014 07:41, Simon Glass wrote:
>>
>> Add a uclass which provides access to SPI buses and includes operations
>> required by SPI.
>>
>> For a time driver model will need to co-exist with the legacy SPI
>> interface
>> so some parts of the header file are changed depending on which is in use.
>> The exports are adjusted also since some functions are not available with
>> driver model.
>>
>> Boards must define CONFIG_DM_SPI to use driver model for SPI.
>
>
> I'm starting to use DM now. Finally! ;)
>
> In my case I'm implementing the Designware master SPI driver for the
> SoCFPGA. And since its a new driver it really makes sense to use DM
> directly. I'm starting to get an overview now. One thing though that I
> noticed is, that the SPI DM implementation seems to be a bit FDT centric.
> Please see below.
>
> <snip>
>
>> diff --git a/drivers/spi/spi-uclass.c b/drivers/spi/spi-uclass.c
>
> <snip>
>
>> +int spi_post_bind(struct udevice *dev)
>> +{
>> +       /* Scan the bus for devices */
>> +       return dm_scan_fdt_node(dev, gd->fdt_blob, dev->of_offset, false);
>> +}
>
>
> SoCFPGA currently does not support CONFIG_OF_CONTROL (no dtb supplied). So
> I'm planning to use platform_data to instantiate the device. Something like
> this in the board file:
>
> static const struct dw_spi_platdata spi_platdata = {
>         .base = SOCFPGA_SPIM0_ADDRESS,
> };
>
> U_BOOT_DEVICE(dw_spi) = {
>         .name = "dw_spi",
>         .platdata = &spi_platdata,
> };
>
> What would the non-fdt method have to be to bind the device in
> spi_post_bind() above?

At present dm-spi support mostly devicetree driven - Will update you
soon for non-devicetree base.

thanks!
Stefan Roese Oct. 29, 2014, 4:16 p.m. UTC | #4
Hi Jagan!

On 29.10.2014 16:39, Jagan Teki wrote:
>>> diff --git a/drivers/spi/spi-uclass.c b/drivers/spi/spi-uclass.c
>>
>> <snip>
>>
>>> +int spi_post_bind(struct udevice *dev)
>>> +{
>>> +       /* Scan the bus for devices */
>>> +       return dm_scan_fdt_node(dev, gd->fdt_blob, dev->of_offset, false);
>>> +}
>>
>>
>> SoCFPGA currently does not support CONFIG_OF_CONTROL (no dtb supplied). So
>> I'm planning to use platform_data to instantiate the device. Something like
>> this in the board file:
>>
>> static const struct dw_spi_platdata spi_platdata = {
>>          .base = SOCFPGA_SPIM0_ADDRESS,
>> };
>>
>> U_BOOT_DEVICE(dw_spi) = {
>>          .name = "dw_spi",
>>          .platdata = &spi_platdata,
>> };
>>
>> What would the non-fdt method have to be to bind the device in
>> spi_post_bind() above?
>
> At present dm-spi support mostly devicetree driven

Yes, I noticed this.

> Will update you soon for non-devicetree base.

So you are working on this too? Do you have a schedule when you have 
something ready for testing? If this is soon I might wait for you here. 
If not, I'll have to dig deeper here myself.

Just checking.

Thanks,
Stefan
Simon Glass Oct. 30, 2014, 1:43 a.m. UTC | #5
Hi Stefan,

On 29 October 2014 07:02, Stefan Roese <sr@denx.de> wrote:
> Hi Simon!
>
> On 14.10.2014 07:41, Simon Glass wrote:
>>
>> Add a uclass which provides access to SPI buses and includes operations
>> required by SPI.
>>
>> For a time driver model will need to co-exist with the legacy SPI
>> interface
>> so some parts of the header file are changed depending on which is in use.
>> The exports are adjusted also since some functions are not available with
>> driver model.
>>
>> Boards must define CONFIG_DM_SPI to use driver model for SPI.
>
>
> I'm starting to use DM now. Finally! ;)

Great!

>
> In my case I'm implementing the Designware master SPI driver for the
> SoCFPGA. And since its a new driver it really makes sense to use DM
> directly. I'm starting to get an overview now. One thing though that I
> noticed is, that the SPI DM implementation seems to be a bit FDT centric.
> Please see below.
>
> <snip>
>
>> diff --git a/drivers/spi/spi-uclass.c b/drivers/spi/spi-uclass.c
>
> <snip>
>
>> +int spi_post_bind(struct udevice *dev)
>> +{
>> +       /* Scan the bus for devices */
>> +       return dm_scan_fdt_node(dev, gd->fdt_blob, dev->of_offset, false);
>> +}
>
>
> SoCFPGA currently does not support CONFIG_OF_CONTROL (no dtb supplied). So

In the kernel I see these files - are you sure it is not supported?

arch/arm/boot/dts/socfpga_arria5.dtsi
arch/arm/boot/dts/socfpga_arria5_socdk.dts
arch/arm/boot/dts/socfpga_cyclone5.dtsi
arch/arm/boot/dts/socfpga_cyclone5_socdk.dts
arch/arm/boot/dts/socfpga_cyclone5_sockit.dts
arch/arm/boot/dts/socfpga_cyclone5_socrates.dts
arch/arm/boot/dts/socfpga.dtsi
arch/arm/boot/dts/socfpga_vt.dts

> I'm planning to use platform_data to instantiate the device. Something like
> this in the board file:
>
> static const struct dw_spi_platdata spi_platdata = {
>         .base = SOCFPGA_SPIM0_ADDRESS,
> };
>
> U_BOOT_DEVICE(dw_spi) = {
>         .name = "dw_spi",
>         .platdata = &spi_platdata,
> };
>
> What would the non-fdt method have to be to bind the device in
> spi_post_bind() above?

We would have to create a new platform data structure that holds the
bus number, CS number and any other information. Then it will need to
cause spi_get_bus_and_cs() to locate it and do the right.

But first let's make sure that you really do have the platform that
doesn't use device tree... If it is ARM my understanding is that it
should.

Also see the SPI driver spi-howto.txt.

Regards,
Simon
Stefan Roese Oct. 30, 2014, 4:38 a.m. UTC | #6
Hi Simon,

On 30.10.2014 02:43, Simon Glass wrote:
>> In my case I'm implementing the Designware master SPI driver for the
>> SoCFPGA. And since its a new driver it really makes sense to use DM
>> directly. I'm starting to get an overview now. One thing though that I
>> noticed is, that the SPI DM implementation seems to be a bit FDT centric.
>> Please see below.
>>
>> <snip>
>>
>>> diff --git a/drivers/spi/spi-uclass.c b/drivers/spi/spi-uclass.c
>>
>> <snip>
>>
>>> +int spi_post_bind(struct udevice *dev)
>>> +{
>>> +       /* Scan the bus for devices */
>>> +       return dm_scan_fdt_node(dev, gd->fdt_blob, dev->of_offset, false);
>>> +}
>>
>>
>> SoCFPGA currently does not support CONFIG_OF_CONTROL (no dtb supplied). So
>
> In the kernel I see these files - are you sure it is not supported?
>
> arch/arm/boot/dts/socfpga_arria5.dtsi
> arch/arm/boot/dts/socfpga_arria5_socdk.dts
> arch/arm/boot/dts/socfpga_cyclone5.dtsi
> arch/arm/boot/dts/socfpga_cyclone5_socdk.dts
> arch/arm/boot/dts/socfpga_cyclone5_sockit.dts
> arch/arm/boot/dts/socfpga_cyclone5_socrates.dts
> arch/arm/boot/dts/socfpga.dtsi
> arch/arm/boot/dts/socfpga_vt.dts

Sure. In the kernel dts is supported. Only U-Boot for SoCFPGA has no DT 
support (right now).

>> I'm planning to use platform_data to instantiate the device. Something like
>> this in the board file:
>>
>> static const struct dw_spi_platdata spi_platdata = {
>>          .base = SOCFPGA_SPIM0_ADDRESS,
>> };
>>
>> U_BOOT_DEVICE(dw_spi) = {
>>          .name = "dw_spi",
>>          .platdata = &spi_platdata,
>> };
>>
>> What would the non-fdt method have to be to bind the device in
>> spi_post_bind() above?
>
> We would have to create a new platform data structure that holds the
> bus number, CS number and any other information. Then it will need to
> cause spi_get_bus_and_cs() to locate it and do the right.
>
> But first let's make sure that you really do have the platform that
> doesn't use device tree... If it is ARM my understanding is that it
> should.

It does. In the kernel. I think I'll dive into adding DT support for 
SoCFPGA for a few hours. Perhaps thats even easier than adding 
platform-data support to the SPI DM. Lets see how this goes...

> Also see the SPI driver spi-howto.txt.

Thanks,
Stefan
Simon Glass Oct. 31, 2014, 1:30 a.m. UTC | #7
Hi Stefan,

On 29 October 2014 22:38, Stefan Roese <sr@denx.de> wrote:
> Hi Simon,
>
> On 30.10.2014 02:43, Simon Glass wrote:
>>>
>>> In my case I'm implementing the Designware master SPI driver for the
>>> SoCFPGA. And since its a new driver it really makes sense to use DM
>>> directly. I'm starting to get an overview now. One thing though that I
>>> noticed is, that the SPI DM implementation seems to be a bit FDT centric.
>>> Please see below.
>>>
>>> <snip>
>>>
>>>> diff --git a/drivers/spi/spi-uclass.c b/drivers/spi/spi-uclass.c
>>>
>>>
>>> <snip>
>>>
>>>> +int spi_post_bind(struct udevice *dev)
>>>> +{
>>>> +       /* Scan the bus for devices */
>>>> +       return dm_scan_fdt_node(dev, gd->fdt_blob, dev->of_offset,
>>>> false);
>>>> +}
>>>
>>>
>>>
>>> SoCFPGA currently does not support CONFIG_OF_CONTROL (no dtb supplied).
>>> So
>>
>>
>> In the kernel I see these files - are you sure it is not supported?
>>
>> arch/arm/boot/dts/socfpga_arria5.dtsi
>> arch/arm/boot/dts/socfpga_arria5_socdk.dts
>> arch/arm/boot/dts/socfpga_cyclone5.dtsi
>> arch/arm/boot/dts/socfpga_cyclone5_socdk.dts
>> arch/arm/boot/dts/socfpga_cyclone5_sockit.dts
>> arch/arm/boot/dts/socfpga_cyclone5_socrates.dts
>> arch/arm/boot/dts/socfpga.dtsi
>> arch/arm/boot/dts/socfpga_vt.dts
>
>
> Sure. In the kernel dts is supported. Only U-Boot for SoCFPGA has no DT
> support (right now).
>
>>> I'm planning to use platform_data to instantiate the device. Something
>>> like
>>> this in the board file:
>>>
>>> static const struct dw_spi_platdata spi_platdata = {
>>>          .base = SOCFPGA_SPIM0_ADDRESS,
>>> };
>>>
>>> U_BOOT_DEVICE(dw_spi) = {
>>>          .name = "dw_spi",
>>>          .platdata = &spi_platdata,
>>> };
>>>
>>> What would the non-fdt method have to be to bind the device in
>>> spi_post_bind() above?
>>
>>
>> We would have to create a new platform data structure that holds the
>> bus number, CS number and any other information. Then it will need to
>> cause spi_get_bus_and_cs() to locate it and do the right.
>>
>> But first let's make sure that you really do have the platform that
>> doesn't use device tree... If it is ARM my understanding is that it
>> should.
>
>
> It does. In the kernel. I think I'll dive into adding DT support for SoCFPGA
> for a few hours. Perhaps thats even easier than adding platform-data support
> to the SPI DM. Lets see how this goes...

Good luck. If you use CONFIG_OF_SEPARATE you will get u-boot-dtb.bin
and hopefully it will just work. For CONFIG_OF_EMBED I see a bug was
introduced so I need to do a patch for that.

>
>> Also see the SPI driver spi-howto.txt.
>
>
> Thanks,
> Stefan
>

Regards,
Simon
Stefan Roese Oct. 31, 2014, 8:01 a.m. UTC | #8
Hi Simon,

On 31.10.2014 02:30, Simon Glass wrote:
>>> But first let's make sure that you really do have the platform that
>>> doesn't use device tree... If it is ARM my understanding is that it
>>> should.
>>
>>
>> It does. In the kernel. I think I'll dive into adding DT support for SoCFPGA
>> for a few hours. Perhaps thats even easier than adding platform-data support
>> to the SPI DM. Lets see how this goes...
>
> Good luck. If you use CONFIG_OF_SEPARATE you will get u-boot-dtb.bin
> and hopefully it will just work. For CONFIG_OF_EMBED I see a bug was
> introduced so I need to do a patch for that.

I didn't add any of those defines above. Just CONFIG_OF_CONTROL seems to 
be needed.

I already posted a patch for DT support for SoCFPGA yesterday. And with 
the addition of the aliases for the spi DT nodes I'm now able use and 
test the driver...

Thanks,
Stefan
Simon Glass Nov. 1, 2014, 3:12 p.m. UTC | #9
Hi Stefan,

On 31 October 2014 02:01, Stefan Roese <sr@denx.de> wrote:
> Hi Simon,
>
> On 31.10.2014 02:30, Simon Glass wrote:
>>>>
>>>> But first let's make sure that you really do have the platform that
>>>> doesn't use device tree... If it is ARM my understanding is that it
>>>> should.
>>>
>>>
>>>
>>> It does. In the kernel. I think I'll dive into adding DT support for
>>> SoCFPGA
>>> for a few hours. Perhaps thats even easier than adding platform-data
>>> support
>>> to the SPI DM. Lets see how this goes...
>>
>>
>> Good luck. If you use CONFIG_OF_SEPARATE you will get u-boot-dtb.bin
>> and hopefully it will just work. For CONFIG_OF_EMBED I see a bug was
>> introduced so I need to do a patch for that.
>
>
> I didn't add any of those defines above. Just CONFIG_OF_CONTROL seems to be
> needed.
>
> I already posted a patch for DT support for SoCFPGA yesterday. And with the
> addition of the aliases for the spi DT nodes I'm now able use and test the
> driver...

Ah great. I'm pleased it went so smoothly for you!

Regards,
Simon
diff mbox

Patch

diff --git a/common/exports.c b/common/exports.c
index b97ca48..88fcfc8 100644
--- a/common/exports.c
+++ b/common/exports.c
@@ -27,10 +27,12 @@  unsigned long get_version(void)
 # define i2c_write         dummy
 # define i2c_read          dummy
 #endif
-#ifndef CONFIG_CMD_SPI
+#if !defined(CONFIG_CMD_SPI) || defined(CONFIG_DM_SPI)
 # define spi_init          dummy
 # define spi_setup_slave   dummy
 # define spi_free_slave    dummy
+#endif
+#ifndef CONFIG_CMD_SPI
 # define spi_claim_bus     dummy
 # define spi_release_bus   dummy
 # define spi_xfer          dummy
diff --git a/drivers/spi/Makefile b/drivers/spi/Makefile
index f02c35a..d1f1dd0 100644
--- a/drivers/spi/Makefile
+++ b/drivers/spi/Makefile
@@ -6,7 +6,11 @@ 
 #
 
 # There are many options which enable SPI, so make this library available
+ifdef CONFIG_DM_SPI
+obj-y += spi-uclass.o
+else
 obj-y += spi.o
+endif
 
 obj-$(CONFIG_EP93XX_SPI) += ep93xx_spi.o
 obj-$(CONFIG_ALTERA_SPI) += altera_spi.o
diff --git a/drivers/spi/spi-uclass.c b/drivers/spi/spi-uclass.c
new file mode 100644
index 0000000..13c6b77
--- /dev/null
+++ b/drivers/spi/spi-uclass.c
@@ -0,0 +1,390 @@ 
+/*
+ * Copyright (c) 2014 Google, Inc
+ *
+ * SPDX-License-Identifier:	GPL-2.0+
+ */
+
+#include <common.h>
+#include <dm.h>
+#include <errno.h>
+#include <fdtdec.h>
+#include <malloc.h>
+#include <spi.h>
+#include <dm/device-internal.h>
+#include <dm/uclass-internal.h>
+#include <dm/root.h>
+#include <dm/lists.h>
+#include <dm/util.h>
+
+DECLARE_GLOBAL_DATA_PTR;
+
+static int spi_set_speed_mode(struct udevice *bus, int speed, int mode)
+{
+	struct dm_spi_ops *ops;
+	int ret;
+
+	ops = spi_get_ops(bus);
+	if (ops->set_speed)
+		ret = ops->set_speed(bus, speed);
+	else
+		ret = -EINVAL;
+	if (ret) {
+		printf("Cannot set speed (err=%d)\n", ret);
+		return ret;
+	}
+
+	if (ops->set_mode)
+		ret = ops->set_mode(bus, mode);
+	else
+		ret = -EINVAL;
+	if (ret) {
+		printf("Cannot set mode (err=%d)\n", ret);
+		return ret;
+	}
+
+	return 0;
+}
+
+int spi_claim_bus(struct spi_slave *slave)
+{
+	struct udevice *dev = slave->dev;
+	struct udevice *bus = dev->parent;
+	struct dm_spi_ops *ops = spi_get_ops(bus);
+	struct dm_spi_bus *spi = bus->uclass_priv;
+	int speed;
+	int ret;
+
+	speed = slave->max_hz;
+	if (spi->max_hz) {
+		if (speed)
+			speed = min(speed, spi->max_hz);
+		else
+			speed = spi->max_hz;
+	}
+	if (!speed)
+		speed = 100000;
+	ret = spi_set_speed_mode(bus, speed, slave->mode);
+	if (ret)
+		return ret;
+
+	return ops->claim_bus ? ops->claim_bus(bus) : 0;
+}
+
+void spi_release_bus(struct spi_slave *slave)
+{
+	struct udevice *dev = slave->dev;
+	struct udevice *bus = dev->parent;
+	struct dm_spi_ops *ops = spi_get_ops(bus);
+
+	if (ops->release_bus)
+		ops->release_bus(bus);
+}
+
+int spi_xfer(struct spi_slave *slave, unsigned int bitlen,
+	     const void *dout, void *din, unsigned long flags)
+{
+	struct udevice *dev = slave->dev;
+	struct udevice *bus = dev->parent;
+
+	if (bus->uclass->uc_drv->id != UCLASS_SPI)
+		return -EOPNOTSUPP;
+
+	return spi_get_ops(bus)->xfer(dev, bitlen, dout, din, flags);
+}
+
+int spi_post_bind(struct udevice *dev)
+{
+	/* Scan the bus for devices */
+	return dm_scan_fdt_node(dev, gd->fdt_blob, dev->of_offset, false);
+}
+
+int spi_post_probe(struct udevice *dev)
+{
+	struct dm_spi_bus *spi = dev->uclass_priv;
+
+	spi->max_hz = fdtdec_get_int(gd->fdt_blob, dev->of_offset,
+				     "spi-max-frequency", 0);
+
+	return 0;
+}
+
+int spi_chip_select(struct udevice *dev)
+{
+	struct spi_slave *slave = dev_get_parentdata(dev);
+
+	return slave ? slave->cs : -ENOENT;
+}
+
+/**
+ * spi_find_chip_select() - Find the slave attached to chip select
+ *
+ * @bus:	SPI bus to search
+ * @cs:		Chip select to look for
+ * @devp:	Returns the slave device if found
+ * @return 0 if found, -ENODEV on error
+ */
+static int spi_find_chip_select(struct udevice *bus, int cs,
+				struct udevice **devp)
+{
+	struct udevice *dev;
+
+	for (device_find_first_child(bus, &dev); dev;
+	     device_find_next_child(&dev)) {
+		struct spi_slave store;
+		struct spi_slave *slave = dev_get_parentdata(dev);
+
+		if (!slave)  {
+			slave = &store;
+			spi_ofdata_to_platdata(gd->fdt_blob, dev->of_offset,
+					       slave);
+		}
+		debug("%s: slave=%p, cs=%d\n", __func__, slave,
+		      slave ? slave->cs : -1);
+		if (slave && slave->cs == cs) {
+			*devp = dev;
+			return 0;
+		}
+	}
+
+	return -ENODEV;
+}
+
+int spi_cs_is_valid(unsigned int busnum, unsigned int cs)
+{
+	struct spi_cs_info info;
+	struct udevice *bus;
+	int ret;
+
+	ret = uclass_find_device_by_seq(UCLASS_SPI, busnum, false, &bus);
+	if (ret) {
+		debug("%s: No bus %d\n", __func__, busnum);
+		return ret;
+	}
+
+	return spi_cs_info(bus, cs, &info);
+}
+
+int spi_cs_info(struct udevice *bus, uint cs, struct spi_cs_info *info)
+{
+	struct spi_cs_info local_info;
+	struct dm_spi_ops *ops;
+	int ret;
+
+	if (!info)
+		info = &local_info;
+
+	/* If there is a device attached, return it */
+	info->dev = NULL;
+	ret = spi_find_chip_select(bus, cs, &info->dev);
+	if (!ret)
+		return 0;
+
+	/*
+	 * Otherwise ask the driver. For the moment we don't have CS info.
+	 * When we do we could provide the driver with a helper function
+	 * to figure out what chip selects are valid, or just handle the
+	 * request.
+	 */
+	ops = spi_get_ops(bus);
+	if (ops->cs_info)
+		return ops->cs_info(bus, cs, info);
+
+	/*
+	 * We could assume there is at least one valid chip select, but best
+	 * to be sure and return an error in this case. The driver didn't
+	 * care enough to tell us.
+	 */
+	return -ENODEV;
+}
+
+int spi_bind_device(struct udevice *bus, int cs, const char *drv_name,
+		    const char *dev_name, struct udevice **devp)
+{
+	struct driver *drv;
+	int ret;
+
+	drv = lists_driver_lookup_name(drv_name);
+	if (!drv) {
+		printf("Cannot find driver '%s'\n", drv_name);
+		return -ENOENT;
+	}
+	ret = device_bind(bus, drv, dev_name, NULL, -1, devp);
+	if (ret) {
+		printf("Cannot create device named '%s' (err=%d)\n",
+		       dev_name, ret);
+		return ret;
+	}
+
+	return 0;
+}
+
+int spi_find_bus_and_cs(int busnum, int cs, struct udevice **busp,
+			struct udevice **devp)
+{
+	struct udevice *bus, *dev;
+	int ret;
+
+	ret = uclass_find_device_by_seq(UCLASS_SPI, busnum, false, &bus);
+	if (ret) {
+		debug("%s: No bus %d\n", __func__, busnum);
+		return ret;
+	}
+	ret = spi_find_chip_select(bus, cs, &dev);
+	if (ret) {
+		debug("%s: No cs %d\n", __func__, cs);
+		return ret;
+	}
+	*busp = bus;
+	*devp = dev;
+
+	return ret;
+}
+
+int spi_get_bus_and_cs(int busnum, int cs, int speed, int mode,
+		       const char *drv_name, const char *dev_name,
+		       struct udevice **busp, struct spi_slave **devp)
+{
+	struct udevice *bus, *dev;
+	struct spi_slave *slave;
+	bool created = false;
+	int ret;
+
+	ret = uclass_get_device_by_seq(UCLASS_SPI, busnum, &bus);
+	if (ret) {
+		printf("Invalid bus %d (err=%d)\n", busnum, ret);
+		return ret;
+	}
+	ret = spi_find_chip_select(bus, cs, &dev);
+
+	/*
+	 * If there is no such device, create one automatically. This means
+	 * that we don't need a device tree node or platform data for the
+	 * SPI flash chip - we will bind to the correct driver.
+	 */
+	if (ret == -ENODEV && drv_name) {
+		debug("%s: Binding new device '%s', busnum=%d, cs=%d, driver=%s\n",
+		      __func__, dev_name, busnum, cs, drv_name);
+		ret = spi_bind_device(bus, cs, drv_name, dev_name, &dev);
+		if (ret)
+			return ret;
+		created = true;
+	} else if (ret) {
+		printf("Invalid chip select %d:%d (err=%d)\n", busnum, cs,
+		       ret);
+		return ret;
+	}
+
+	if (!device_active(dev)) {
+		slave = (struct spi_slave *)calloc(1,
+						   sizeof(struct spi_slave));
+		if (!slave) {
+			ret = -ENOMEM;
+			goto err;
+		}
+
+		ret = spi_ofdata_to_platdata(gd->fdt_blob, dev->of_offset,
+					     slave);
+		if (ret)
+			goto err;
+		slave->cs = cs;
+		slave->dev = dev;
+		ret = device_probe_child(dev, slave);
+		free(slave);
+		if (ret)
+			goto err;
+	}
+
+	ret = spi_set_speed_mode(bus, speed, mode);
+	if (ret)
+		goto err;
+
+	*busp = bus;
+	*devp = dev_get_parentdata(dev);
+	debug("%s: bus=%p, slave=%p\n", __func__, bus, *devp);
+
+	return 0;
+
+err:
+	if (created) {
+		device_remove(dev);
+		device_unbind(dev);
+	}
+
+	return ret;
+}
+
+/* Compatibility function - to be removed */
+struct spi_slave *spi_setup_slave_fdt(const void *blob, int node,
+				      int bus_node)
+{
+	struct udevice *bus, *dev;
+	int ret;
+
+	ret = uclass_get_device_by_of_offset(UCLASS_SPI, bus_node, &bus);
+	if (ret)
+		return NULL;
+	ret = device_get_child_by_of_offset(bus, node, &dev);
+	if (ret)
+		return NULL;
+	return dev_get_parentdata(dev);
+}
+
+/* Compatibility function - to be removed */
+struct spi_slave *spi_setup_slave(unsigned int busnum, unsigned int cs,
+				  unsigned int speed, unsigned int mode)
+{
+	struct spi_slave *slave;
+	struct udevice *dev;
+	int ret;
+
+	ret = spi_get_bus_and_cs(busnum, cs, speed, mode, NULL, 0, &dev,
+				  &slave);
+	if (ret)
+		return NULL;
+
+	return slave;
+}
+
+void spi_free_slave(struct spi_slave *slave)
+{
+	device_remove(slave->dev);
+	slave->dev = NULL;
+}
+
+int spi_ofdata_to_platdata(const void *blob, int node,
+			   struct spi_slave *spi)
+{
+	int mode = 0;
+
+	spi->cs = fdtdec_get_int(blob, node, "reg", -1);
+	spi->max_hz = fdtdec_get_int(blob, node, "spi-max-frequency", 0);
+	if (fdtdec_get_bool(blob, node, "spi-cpol"))
+		mode |= SPI_CPOL;
+	if (fdtdec_get_bool(blob, node, "spi-cpha"))
+		mode |= SPI_CPHA;
+	if (fdtdec_get_bool(blob, node, "spi-cs-high"))
+		mode |= SPI_CS_HIGH;
+	if (fdtdec_get_bool(blob, node, "spi-half-duplex"))
+		mode |= SPI_PREAMBLE;
+	spi->mode = mode;
+
+	return 0;
+}
+
+UCLASS_DRIVER(spi) = {
+	.id		= UCLASS_SPI,
+	.name		= "spi",
+	.post_bind	= spi_post_bind,
+	.post_probe	= spi_post_probe,
+	.per_device_auto_alloc_size = sizeof(struct dm_spi_bus),
+};
+
+UCLASS_DRIVER(spi_generic) = {
+	.id		= UCLASS_SPI_GENERIC,
+	.name		= "spi_generic",
+};
+
+U_BOOT_DRIVER(spi_generic_drv) = {
+	.name		= "spi_generic_drv",
+	.id		= UCLASS_SPI_GENERIC,
+};
diff --git a/include/dm/uclass-id.h b/include/dm/uclass-id.h
index 7f0e37b..0afdc75 100644
--- a/include/dm/uclass-id.h
+++ b/include/dm/uclass-id.h
@@ -22,6 +22,8 @@  enum uclass_id {
 	/* U-Boot uclasses start here */
 	UCLASS_GPIO,		/* Bank of general-purpose I/O pins */
 	UCLASS_SERIAL,		/* Serial UART */
+	UCLASS_SPI,		/* SPI bus */
+	UCLASS_SPI_GENERIC,	/* Generic SPI flash target */
 
 	UCLASS_COUNT,
 	UCLASS_INVALID = -1,
diff --git a/include/spi.h b/include/spi.h
index b673be2..89949b1 100644
--- a/include/spi.h
+++ b/include/spi.h
@@ -54,12 +54,31 @@ 
 
 #define SPI_DEFAULT_WORDLEN 8
 
+#ifdef CONFIG_DM_SPI
+struct dm_spi_bus {
+	uint max_hz;
+};
+
+#endif /* CONFIG_DM_SPI */
+
 /**
  * struct spi_slave - Representation of a SPI slave
  *
- * Drivers are expected to extend this with controller-specific data.
+ * For driver model this is the per-child data used by the SPI bus. It can
+ * be accessed using dev_get_parentdata() on the slave device. Each SPI
+ * driver should define this child data in its U_BOOT_DRIVER() definition:
+ *
+ *	.per_child_auto_alloc_size	= sizeof(struct spi_slave),
  *
- * @bus:		ID of the bus that the slave is attached to.
+ * If not using driver model, drivers are expected to extend this with
+ * controller-specific data.
+ *
+ * @dev:		SPI slave device
+ * @max_hz:		Maximum speed for this slave
+ * @mode:		SPI mode to use for this slave (see SPI mode flags)
+ * @bus:		ID of the bus that the slave is attached to. For
+ *			driver model this is the sequence number of the SPI
+ *			bus (bus->seq) so does not need to be stored
  * @cs:			ID of the chip select connected to the slave.
  * @op_mode_rx:		SPI RX operation mode.
  * @op_mode_tx:		SPI TX operation mode.
@@ -71,7 +90,13 @@ 
  * @flags:		Indication of SPI flags.
  */
 struct spi_slave {
+#ifdef CONFIG_DM_SPI
+	struct udevice *dev;	/* struct spi_slave is dev->parentdata */
+	uint max_hz;
+	uint mode;
+#else
 	unsigned int bus;
+#endif
 	unsigned int cs;
 	u8 op_mode_rx;
 	u8 op_mode_tx;
@@ -228,8 +253,9 @@  int  spi_xfer(struct spi_slave *slave, unsigned int bitlen, const void *dout,
  * Returns: 1 if bus:cs identifies a valid chip on this board, 0
  * otherwise.
  */
-int  spi_cs_is_valid(unsigned int bus, unsigned int cs);
+int spi_cs_is_valid(unsigned int bus, unsigned int cs);
 
+#ifndef CONFIG_DM_SPI
 /**
  * Activate a SPI chipselect.
  * This function is provided by the board code when using a driver
@@ -255,6 +281,7 @@  void spi_cs_deactivate(struct spi_slave *slave);
  * @hz:		The transfer speed
  */
 void spi_set_speed(struct spi_slave *slave, uint hz);
+#endif
 
 /**
  * Write 8 bits, then read 8 bits.
@@ -305,4 +332,225 @@  struct spi_slave *spi_setup_slave_fdt(const void *blob, int slave_node,
 struct spi_slave *spi_base_setup_slave_fdt(const void *blob, int busnum,
 					   int node);
 
+#ifdef CONFIG_DM_SPI
+
+/**
+ * struct spi_cs_info - Information about a bus chip select
+ *
+ * @dev:	Connected device, or NULL if none
+ */
+struct spi_cs_info {
+	struct udevice *dev;
+};
+
+/**
+ * struct struct dm_spi_ops - Driver model SPI operations
+ *
+ * The uclass interface is implemented by all SPI devices which use
+ * driver model.
+ */
+struct dm_spi_ops {
+	/**
+	 * Claim the bus and prepare it for communication.
+	 *
+	 * The device provided is the slave device. It's parent controller
+	 * will be used to provide the communication.
+	 *
+	 * This must be called before doing any transfers with a SPI slave. It
+	 * will enable and initialize any SPI hardware as necessary, and make
+	 * sure that the SCK line is in the correct idle state. It is not
+	 * allowed to claim the same bus for several slaves without releasing
+	 * the bus in between.
+	 *
+	 * @bus:	The SPI slave
+	 *
+	 * Returns: 0 if the bus was claimed successfully, or a negative value
+	 * if it wasn't.
+	 */
+	int (*claim_bus)(struct udevice *bus);
+
+	/**
+	 * Release the SPI bus
+	 *
+	 * This must be called once for every call to spi_claim_bus() after
+	 * all transfers have finished. It may disable any SPI hardware as
+	 * appropriate.
+	 *
+	 * @bus:	The SPI slave
+	 */
+	int (*release_bus)(struct udevice *bus);
+
+	/**
+	 * Set the word length for SPI transactions
+	 *
+	 * Set the word length (number of bits per word) for SPI transactions.
+	 *
+	 * @bus:	The SPI slave
+	 * @wordlen:	The number of bits in a word
+	 *
+	 * Returns: 0 on success, -ve on failure.
+	 */
+	int (*set_wordlen)(struct udevice *bus, unsigned int wordlen);
+
+	/**
+	 * SPI transfer
+	 *
+	 * This writes "bitlen" bits out the SPI MOSI port and simultaneously
+	 * clocks "bitlen" bits in the SPI MISO port.  That's just the way SPI
+	 * works.
+	 *
+	 * The source of the outgoing bits is the "dout" parameter and the
+	 * destination of the input bits is the "din" parameter.  Note that
+	 * "dout" and "din" can point to the same memory location, in which
+	 * case the input data overwrites the output data (since both are
+	 * buffered by temporary variables, this is OK).
+	 *
+	 * spi_xfer() interface:
+	 * @dev:	The slave device to communicate with
+	 * @bitlen:	How many bits to write and read.
+	 * @dout:	Pointer to a string of bits to send out.  The bits are
+	 *		held in a byte array and are sent MSB first.
+	 * @din:	Pointer to a string of bits that will be filled in.
+	 * @flags:	A bitwise combination of SPI_XFER_* flags.
+	 *
+	 * Returns: 0 on success, not -1 on failure
+	 */
+	int (*xfer)(struct udevice *dev, unsigned int bitlen, const void *dout,
+		    void *din, unsigned long flags);
+
+	/**
+	 * Set transfer speed.
+	 * This sets a new speed to be applied for next spi_xfer().
+	 * @bus:	The SPI bus
+	 * @hz:		The transfer speed
+	 * @return 0 if OK, -ve on error
+	 */
+	int (*set_speed)(struct udevice *bus, uint hz);
+
+	/**
+	 * Set the SPI mode/flags
+	 *
+	 * It is unclear if we want to set speed and mode together instead
+	 * of separately.
+	 *
+	 * @bus:	The SPI bus
+	 * @mode:	Requested SPI mode (SPI_... flags)
+	 * @return 0 if OK, -ve on error
+	 */
+	int (*set_mode)(struct udevice *bus, uint mode);
+
+	/**
+	 * Get information on a chip select
+	 *
+	 * This is only called when the SPI uclass does not know about a
+	 * chip select, i.e. it has no attached device. It gives the driver
+	 * a chance to allow activity on that chip select even so.
+	 *
+	 * @bus:	The SPI bus
+	 * @cs:		The chip select (0..n-1)
+	 * @info:	Returns information about the chip select, if valid.
+	 *		On entry info->dev is NULL
+	 * @return 0 if OK (and @info is set up), -ENODEV if the chip select
+	 *	   is invalid, other -ve value on error
+	 */
+	int (*cs_info)(struct udevice *bus, uint cs, struct spi_cs_info *info);
+};
+
+/**
+ * spi_find_bus_and_cs() - Find bus and slave devices by number
+ *
+ * Given a bus number and chip select, this finds the corresponding bus
+ * device and slave device. Neither device is activated by this function,
+ * although they may have been activated previously.
+ *
+ * @busnum:	SPI bus number
+ * @cs:		Chip select to look for
+ * @busp:	Returns bus device
+ * @devp:	Return slave device
+ * @return 0 if found, -ENODEV on error
+ */
+int spi_find_bus_and_cs(int busnum, int cs, struct udevice **busp,
+			struct udevice **devp);
+
+/**
+ * spi_get_bus_and_cs() - Find and activate bus and slave devices by number
+ *
+ * Given a bus number and chip select, this finds the corresponding bus
+ * device and slave device.
+ *
+ * If no such slave exists, and drv_name is not NULL, then a new slave device
+ * is automatically bound on this chip select.
+ *
+ * Ths new slave device is probed ready for use with the given speed and mode.
+ *
+ * @busnum:	SPI bus number
+ * @cs:		Chip select to look for
+ * @speed:	SPI speed to use for this slave
+ * @mode:	SPI mode to use for this slave
+ * @drv_name:	Name of driver to attach to this chip select
+ * @dev_name:	Name of the new device thus created
+ * @busp:	Returns bus device
+ * @devp:	Return slave device
+ * @return 0 if found, -ve on error
+ */
+int spi_get_bus_and_cs(int busnum, int cs, int speed, int mode,
+			const char *drv_name, const char *dev_name,
+			struct udevice **busp, struct spi_slave **devp);
+
+/**
+ * spi_chip_select() - Get the chip select for a slave
+ *
+ * @return the chip select this slave is attached to
+ */
+int spi_chip_select(struct udevice *slave);
+
+/**
+ * spi_bind_device() - bind a device to a bus's chip select
+ *
+ * This binds a new device to an given chip select (which must be unused).
+ *
+ * @bus:	SPI bus to search
+ * @cs:		Chip select to attach to
+ * @drv_name:	Name of driver to attach to this chip select
+ * @dev_name:	Name of the new device thus created
+ * @devp:	Returns the newly bound device
+ */
+int spi_bind_device(struct udevice *bus, int cs, const char *drv_name,
+		    const char *dev_name, struct udevice **devp);
+
+/**
+ * spi_ofdata_to_platdata() - decode standard SPI platform data
+ *
+ * This decodes the speed and mode from a device tree node and puts it into
+ * the spi_slave structure.
+ *
+ * @blob:	Device tree blob
+ * @node:	Node offset to read from
+ * @spi:	Place to put the decoded information
+ */
+int spi_ofdata_to_platdata(const void *blob, int node, struct spi_slave *spi);
+
+/**
+ * spi_cs_info() - Check information on a chip select
+ *
+ * This checks a particular chip select on a bus to see if it has a device
+ * attached, or is even valid.
+ *
+ * @bus:	The SPI bus
+ * @cs:		The chip select (0..n-1)
+ * @info:	Returns information about the chip select, if valid
+ * @return 0 if OK (and @info is set up), -ENODEV if the chip select
+ *	   is invalid, other -ve value on error
+ */
+int spi_cs_info(struct udevice *bus, uint cs, struct spi_cs_info *info);
+
+struct sandbox_state;
+int sandbox_spi_get_emul(struct sandbox_state *state,
+			 struct udevice *bus, struct udevice *slave,
+			 struct udevice **emulp);
+
+/* Access the serial operations for a device */
+#define spi_get_ops(dev)	((struct dm_spi_ops *)(dev)->driver->ops)
+#endif /* CONFIG_DM_SPI */
+
 #endif	/* _SPI_H_ */