| // SPDX-License-Identifier: GPL-2.0+ | |
| /* | |
| * Copyright (c) 2014 Google, Inc | |
| */ | |
| #define LOG_CATEGORY UCLASS_SPI | |
| #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_set_speed(struct spi_slave *slave, uint hz) | |
| { | |
| struct dm_spi_ops *ops; | |
| int ret; | |
| ops = spi_get_ops(slave->dev->parent); | |
| if (ops->set_speed) | |
| ret = ops->set_speed(slave->dev->parent, hz); | |
| else | |
| ret = -EINVAL; | |
| if (ret) | |
| dev_err(slave->dev, "Cannot set speed (err=%d)\n", ret); | |
| return ret; | |
| } | |
| 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_REAL) | |
| 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_REAL)) { | |
| struct dm_spi_bus *spi = dev_get_uclass_priv(bus); | |
| spi->max_hz = dev_read_u32_default(bus, "spi-max-frequency", 0); | |
| } | |
| 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, struct udevice **busp, | |
| struct spi_slave **devp) | |
| { | |
| struct udevice *bus, *dev; | |
| struct dm_spi_bus *bus_data; | |
| struct spi_slave *slave; | |
| 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 (ret) { | |
| dev_err(bus, "Invalid chip select %d:%d (err=%d)\n", busnum, cs, ret); | |
| return ret; | |
| } | |
| 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; | |
| } | |
| *busp = bus; | |
| *devp = slave; | |
| return 0; | |
| err: | |
| log_debug("%s: Error path, device '%s'\n", __func__, dev->name); | |
| 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_REAL) | |
| .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_REAL) | |
| .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, | |
| }; |