diff options
Diffstat (limited to 'roms/u-boot/drivers/spi/spi-uclass.c')
-rw-r--r-- | roms/u-boot/drivers/spi/spi-uclass.c | 555 |
1 files changed, 555 insertions, 0 deletions
diff --git a/roms/u-boot/drivers/spi/spi-uclass.c b/roms/u-boot/drivers/spi/spi-uclass.c new file mode 100644 index 000000000..d867b2780 --- /dev/null +++ b/roms/u-boot/drivers/spi/spi-uclass.c @@ -0,0 +1,555 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (c) 2014 Google, Inc + */ + +#define LOG_CATEGORY UCLASS_SPI + +#include <common.h> +#include <dm.h> +#include <errno.h> +#include <log.h> +#include <malloc.h> +#include <spi.h> +#include <spi-mem.h> +#include <dm/device_compat.h> +#include <asm/global_data.h> +#include <dm/device-internal.h> +#include <dm/uclass-internal.h> +#include <dm/lists.h> +#include <dm/util.h> + +DECLARE_GLOBAL_DATA_PTR; + +#define SPI_DEFAULT_SPEED_HZ 100000 + +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) { + dev_err(bus, "Cannot set speed (err=%d)\n", ret); + return ret; + } + + if (ops->set_mode) + ret = ops->set_mode(bus, mode); + else + ret = -EINVAL; + if (ret) { + dev_err(bus, "Cannot set mode (err=%d)\n", ret); + return ret; + } + + return 0; +} + +int dm_spi_claim_bus(struct udevice *dev) +{ + struct udevice *bus = dev->parent; + struct dm_spi_ops *ops = spi_get_ops(bus); + struct dm_spi_bus *spi = dev_get_uclass_priv(bus); + struct spi_slave *slave = dev_get_parent_priv(dev); + uint speed, mode; + + speed = slave->max_hz; + mode = slave->mode; + + if (spi->max_hz) { + if (speed) + speed = min(speed, spi->max_hz); + else + speed = spi->max_hz; + } + if (!speed) + speed = SPI_DEFAULT_SPEED_HZ; + + if (speed != spi->speed || mode != spi->mode) { + int ret = spi_set_speed_mode(bus, speed, slave->mode); + + if (ret) + return log_ret(ret); + + spi->speed = speed; + spi->mode = mode; + } + + return log_ret(ops->claim_bus ? ops->claim_bus(dev) : 0); +} + +void dm_spi_release_bus(struct udevice *dev) +{ + struct udevice *bus = dev->parent; + struct dm_spi_ops *ops = spi_get_ops(bus); + + if (ops->release_bus) + ops->release_bus(dev); +} + +int dm_spi_xfer(struct udevice *dev, unsigned int bitlen, + const void *dout, void *din, unsigned long flags) +{ + struct udevice *bus = dev->parent; + struct dm_spi_ops *ops = spi_get_ops(bus); + + if (bus->uclass->uc_drv->id != UCLASS_SPI) + return -EOPNOTSUPP; + if (!ops->xfer) + return -ENOSYS; + + return ops->xfer(dev, bitlen, dout, din, flags); +} + +int dm_spi_get_mmap(struct udevice *dev, ulong *map_basep, uint *map_sizep, + uint *offsetp) +{ + struct udevice *bus = dev->parent; + struct dm_spi_ops *ops = spi_get_ops(bus); + + if (bus->uclass->uc_drv->id != UCLASS_SPI) + return -EOPNOTSUPP; + if (!ops->get_mmap) + return -ENOSYS; + + return ops->get_mmap(dev, map_basep, map_sizep, offsetp); +} + +int spi_claim_bus(struct spi_slave *slave) +{ + return log_ret(dm_spi_claim_bus(slave->dev)); +} + +void spi_release_bus(struct spi_slave *slave) +{ + dm_spi_release_bus(slave->dev); +} + +int spi_xfer(struct spi_slave *slave, unsigned int bitlen, + const void *dout, void *din, unsigned long flags) +{ + return dm_spi_xfer(slave->dev, bitlen, dout, din, flags); +} + +int spi_write_then_read(struct spi_slave *slave, const u8 *opcode, + size_t n_opcode, const u8 *txbuf, u8 *rxbuf, + size_t n_buf) +{ + unsigned long flags = SPI_XFER_BEGIN; + int ret; + + if (n_buf == 0) + flags |= SPI_XFER_END; + + ret = spi_xfer(slave, n_opcode * 8, opcode, NULL, flags); + if (ret) { + dev_dbg(slave->dev, + "spi: failed to send command (%zu bytes): %d\n", + n_opcode, ret); + } else if (n_buf != 0) { + ret = spi_xfer(slave, n_buf * 8, txbuf, rxbuf, SPI_XFER_END); + if (ret) + dev_dbg(slave->dev, + "spi: failed to transfer %zu bytes of data: %d\n", + n_buf, ret); + } + + return ret; +} + +#if !CONFIG_IS_ENABLED(OF_PLATDATA) +static int spi_child_post_bind(struct udevice *dev) +{ + struct dm_spi_slave_plat *plat = dev_get_parent_plat(dev); + + if (!dev_has_ofnode(dev)) + return 0; + + return spi_slave_of_to_plat(dev, plat); +} +#endif + +static int spi_post_probe(struct udevice *bus) +{ +#if !CONFIG_IS_ENABLED(OF_PLATDATA) + struct dm_spi_bus *spi = dev_get_uclass_priv(bus); + + spi->max_hz = dev_read_u32_default(bus, "spi-max-frequency", 0); +#endif +#if defined(CONFIG_NEEDS_MANUAL_RELOC) + struct dm_spi_ops *ops = spi_get_ops(bus); + static int reloc_done; + + if (!reloc_done) { + if (ops->claim_bus) + ops->claim_bus += gd->reloc_off; + if (ops->release_bus) + ops->release_bus += gd->reloc_off; + if (ops->set_wordlen) + ops->set_wordlen += gd->reloc_off; + if (ops->xfer) + ops->xfer += gd->reloc_off; + if (ops->set_speed) + ops->set_speed += gd->reloc_off; + if (ops->set_mode) + ops->set_mode += gd->reloc_off; + if (ops->cs_info) + ops->cs_info += gd->reloc_off; + if (ops->mem_ops) { + struct spi_controller_mem_ops *mem_ops = + (struct spi_controller_mem_ops *)ops->mem_ops; + if (mem_ops->adjust_op_size) + mem_ops->adjust_op_size += gd->reloc_off; + if (mem_ops->supports_op) + mem_ops->supports_op += gd->reloc_off; + if (mem_ops->exec_op) + mem_ops->exec_op += gd->reloc_off; + } + reloc_done++; + } +#endif + + return 0; +} + +static int spi_child_pre_probe(struct udevice *dev) +{ + struct dm_spi_slave_plat *plat = dev_get_parent_plat(dev); + struct spi_slave *slave = dev_get_parent_priv(dev); + + /* + * This is needed because we pass struct spi_slave around the place + * instead slave->dev (a struct udevice). So we have to have some + * way to access the slave udevice given struct spi_slave. Once we + * change the SPI API to use udevice instead of spi_slave, we can + * drop this. + */ + slave->dev = dev; + + slave->max_hz = plat->max_hz; + slave->mode = plat->mode; + slave->wordlen = SPI_DEFAULT_WORDLEN; + + return 0; +} + +int spi_chip_select(struct udevice *dev) +{ + struct dm_spi_slave_plat *plat = dev_get_parent_plat(dev); + + return plat ? plat->cs : -ENOENT; +} + +int spi_find_chip_select(struct udevice *bus, int cs, struct udevice **devp) +{ + struct dm_spi_ops *ops; + struct spi_cs_info info; + struct udevice *dev; + int ret; + + /* + * 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) { + ret = ops->cs_info(bus, cs, &info); + } else { + /* + * We could assume there is at least one valid chip select. + * The driver didn't care enough to tell us. + */ + ret = 0; + } + + if (ret) { + dev_err(bus, "Invalid cs %d (err=%d)\n", cs, ret); + return ret; + } + + for (device_find_first_child(bus, &dev); dev; + device_find_next_child(&dev)) { + struct dm_spi_slave_plat *plat; + + plat = dev_get_parent_plat(dev); + dev_dbg(bus, "%s: plat=%p, cs=%d\n", __func__, plat, plat->cs); + if (plat->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, &bus); + if (ret) { + log_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; + 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); + return ret == -ENODEV ? 0 : ret; +} + +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, &bus); + if (ret) { + log_debug("%s: No bus %d\n", __func__, busnum); + return ret; + } + ret = spi_find_chip_select(bus, cs, &dev); + if (ret) { + dev_dbg(bus, "%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 dm_spi_slave_plat *plat; + struct dm_spi_bus *bus_data; + struct spi_slave *slave; + bool created = false; + int ret; + +#if CONFIG_IS_ENABLED(OF_PLATDATA) + ret = uclass_first_device_err(UCLASS_SPI, &bus); +#else + ret = uclass_get_device_by_seq(UCLASS_SPI, busnum, &bus); +#endif + if (ret) { + log_err("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) { + dev_dbg(bus, "%s: Binding new device '%s', busnum=%d, cs=%d, driver=%s\n", + __func__, dev_name, busnum, cs, drv_name); + ret = device_bind_driver(bus, drv_name, dev_name, &dev); + if (ret) { + dev_dbg(bus, "%s: Unable to bind driver (ret=%d)\n", + __func__, ret); + return ret; + } + plat = dev_get_parent_plat(dev); + plat->cs = cs; + if (speed) { + plat->max_hz = speed; + } else { + dev_warn(bus, + "Warning: SPI speed fallback to %u kHz\n", + SPI_DEFAULT_SPEED_HZ / 1000); + plat->max_hz = SPI_DEFAULT_SPEED_HZ; + } + plat->mode = mode; + created = true; + } else if (ret) { + dev_err(bus, "Invalid chip select %d:%d (err=%d)\n", busnum, cs, ret); + return ret; + } else if (dev) { + plat = dev_get_parent_plat(dev); + } + + if (!device_active(dev)) { + struct spi_slave *slave; + + ret = device_probe(dev); + if (ret) + goto err; + slave = dev_get_parent_priv(dev); + slave->dev = dev; + } + + slave = dev_get_parent_priv(dev); + bus_data = dev_get_uclass_priv(bus); + + /* + * In case the operation speed is not yet established by + * dm_spi_claim_bus() ensure the bus is configured properly. + */ + if (!bus_data->speed) { + ret = spi_claim_bus(slave); + if (ret) + goto err; + } + + /* In case bus frequency or mode changed, update it. */ + if ((speed && bus_data->speed && bus_data->speed != speed) || + (plat && plat->mode != mode)) { + ret = spi_set_speed_mode(bus, speed, mode); + if (ret) + goto err_speed_mode; + } + + *busp = bus; + *devp = slave; + log_debug("%s: bus=%p, slave=%p\n", __func__, bus, *devp); + + return 0; + +err_speed_mode: + spi_release_bus(slave); +err: + log_debug("%s: Error path, created=%d, device '%s'\n", __func__, + created, dev->name); + if (created) { + device_remove(dev, DM_REMOVE_NORMAL); + device_unbind(dev); + } + + return ret; +} + +/* 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, DM_REMOVE_NORMAL); +} + +int spi_slave_of_to_plat(struct udevice *dev, struct dm_spi_slave_plat *plat) +{ + int mode = 0; + int value; + + plat->cs = dev_read_u32_default(dev, "reg", -1); + plat->max_hz = dev_read_u32_default(dev, "spi-max-frequency", + SPI_DEFAULT_SPEED_HZ); + if (dev_read_bool(dev, "spi-cpol")) + mode |= SPI_CPOL; + if (dev_read_bool(dev, "spi-cpha")) + mode |= SPI_CPHA; + if (dev_read_bool(dev, "spi-cs-high")) + mode |= SPI_CS_HIGH; + if (dev_read_bool(dev, "spi-3wire")) + mode |= SPI_3WIRE; + if (dev_read_bool(dev, "spi-half-duplex")) + mode |= SPI_PREAMBLE; + + /* Device DUAL/QUAD mode */ + value = dev_read_u32_default(dev, "spi-tx-bus-width", 1); + switch (value) { + case 1: + break; + case 2: + mode |= SPI_TX_DUAL; + break; + case 4: + mode |= SPI_TX_QUAD; + break; + case 8: + mode |= SPI_TX_OCTAL; + break; + default: + warn_non_spl("spi-tx-bus-width %d not supported\n", value); + break; + } + + value = dev_read_u32_default(dev, "spi-rx-bus-width", 1); + switch (value) { + case 1: + break; + case 2: + mode |= SPI_RX_DUAL; + break; + case 4: + mode |= SPI_RX_QUAD; + break; + case 8: + mode |= SPI_RX_OCTAL; + break; + default: + warn_non_spl("spi-rx-bus-width %d not supported\n", value); + break; + } + + plat->mode = mode; + + return 0; +} + +UCLASS_DRIVER(spi) = { + .id = UCLASS_SPI, + .name = "spi", + .flags = DM_UC_FLAG_SEQ_ALIAS, +#if CONFIG_IS_ENABLED(OF_CONTROL) && !CONFIG_IS_ENABLED(OF_PLATDATA) + .post_bind = dm_scan_fdt_dev, +#endif + .post_probe = spi_post_probe, + .child_pre_probe = spi_child_pre_probe, + .per_device_auto = sizeof(struct dm_spi_bus), + .per_child_auto = sizeof(struct spi_slave), + .per_child_plat_auto = sizeof(struct dm_spi_slave_plat), +#if !CONFIG_IS_ENABLED(OF_PLATDATA) + .child_post_bind = spi_child_post_bind, +#endif +}; + +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, +}; |