@@ -61,6 +61,10 @@ contain the following properties.
used for MISO. Defaults to 1 if not present.
- spi-half-duplex - (optional) Indicates that the SPI bus should wait for
a header byte before reading data from the slave.
+- calibration-offset - (optional) Check pattern offset location for high-
+ speed calibration.
+- calibration-length - (optional) Check pattern length for high-speed
+ calibration.
Some SPI controllers and devices support Dual and Quad SPI transfer mode.
It allows data in SPI system transferred in 2 wires(DUAL) or 4 wires(QUAD).
@@ -127,6 +127,18 @@ config SPI_FLASH_SOFT_RESET_ON_BOOT
that are not supposed to be handed to U-Boot in Octal DTR mode, even
if they _do_ support the Soft Reset sequence.
+config SPI_FLASH_HS_CALIB
+ bool "Support for high-speed SPI flash calibration"
+ default n
+ help
+ Modern flash chips and controllers that operate at speeds higher than
+ 50MHz require delays in the signal lines to be calibrated. This
+ feature adds a spi-mem API hook to allow controller drivers to
+ perform the calibration during chip probe.
+ Select N here if SPI flash chips or controllers on your platform
+ operate roughly around or less than 50MHz, or if the controller driver
+ doesn't implement the calibration hook.
+
config SPI_FLASH_BAR
bool "SPI flash Bank/Extended address register support"
help
@@ -3230,6 +3230,152 @@ static int spi_nor_select_erase(struct spi_nor *nor,
return 0;
}
+#if CONFIG_IS_ENABLED(SPI_FLASH_HS_CALIB)
+/**
+ * Very basic check to make sure every bit changes in the reference pattern
+ * and that there aren't too many duplicate bytes in a row, such as all FF's
+ * or all zeros.
+ * Returns boolean.
+ */
+static int spi_nor_spimem_validate_calib_data(u8 *buff, size_t len)
+{
+ size_t i;
+ u8 prev;
+ u8 changed_bits = 0;
+ size_t changing = 0;
+
+ if (!buff || !len)
+ return 0;
+
+ prev = *buff;
+ for (i = 1; i < len; ++i) {
+ u8 changed = prev ^ buff[i];
+
+ changed_bits |= changed;
+ if (changed)
+ ++changing;
+ prev = buff[i];
+ }
+ return (changing > (len / 2)) && (changed_bits == 0xff);
+}
+
+/** Returns 0 on success, negative error code on error.
+ */
+static int spi_nor_spimem_calib_read_chk(struct spi_slave *slave)
+{
+ struct spi_nor *nor = dev_get_uclass_priv(slave->dev);
+
+ ssize_t readval = nor->read(nor, nor->calib_off,
+ nor->calib_size,
+ nor->calib_buff);
+ if (readval != nor->calib_size) {
+ if (readval >= 0)
+ return -EIO;
+ return readval;
+ }
+ return (memcmp(nor->calib_ref_buff,
+ nor->calib_buff, nor->calib_size) != 0);
+}
+
+static int spi_nor_spimem_calib(struct spi_nor *nor)
+{
+ int retval = 0;
+
+ if (!nor->calib_buff || !nor->calib_ref_buff)
+ return retval;
+
+ retval = spi_mem_calibrate(nor->spi, spi_nor_spimem_calib_read_chk);
+
+ devm_kfree(nor->dev, nor->calib_buff);
+ nor->calib_buff = NULL;
+ devm_kfree(nor->dev, nor->calib_ref_buff);
+ nor->calib_ref_buff = NULL;
+
+ return retval;
+}
+
+static
+int spi_nor_spimem_calib_init(struct spi_nor *nor,
+ const struct spi_nor_flash_parameter *params,
+ u32 shared_caps_mask)
+{
+ int err;
+ ssize_t readval;
+ ofnode np = dev_ofnode(nor->spi->dev);
+ u8 old_addr_width = nor->addr_width;
+
+ if (!spi_mem_has_calibrate(nor->spi))
+ return 0;
+
+ /* Select a supported 1x read command to obtain calibration
+ * reference data.
+ */
+ shared_caps_mask &= (SNOR_HWCAPS_READ | SNOR_HWCAPS_READ_FAST);
+ err = spi_nor_select_read(nor, params, shared_caps_mask);
+ if (err) {
+ dev_warn(nor->dev,
+ "Skipping SPI-nor calibration: Can't select 1x read settings supported by both the controller and memory.\n");
+ return 0;
+ }
+
+ /* Default to read the first 2 pages of the flash. Usually, this
+ * would contain a bootloader image. While this isn't an ideal
+ * calibration pattern, it's hopefully good enough to get something
+ * working most of the time.
+ */
+ ofnode_read_u32(np, "calibration-offset", &nor->calib_off);
+ ofnode_read_u32(np, "calibration-length", &nor->calib_size);
+ if (!nor->calib_size)
+ nor->calib_size = 2 * nor->page_size;
+
+ if (nor->addr_width == 4 ||
+ ((nor->calib_off + nor->calib_size) > SZ_16M)) {
+ /* Only support a >16MB calibration address if the flash chip
+ * supports 4 byte commands. Otherwise, it would require
+ * enabling 4 byte mode. This isn't handled until later, in
+ * spi_nor_init()
+ */
+ if (!(nor->flags & SPI_NOR_4B_OPCODES)) {
+ dev_warn(nor->dev,
+ "Skipping SPI-nor calibration: calibration currently only supports 3 byte addressing.\n");
+ return 0;
+ }
+ nor->read_opcode = spi_nor_convert_3to4_read(nor->read_opcode);
+ nor->addr_width = 4;
+ } else {
+ nor->addr_width = 3;
+ }
+
+ nor->calib_ref_buff = devm_kmalloc(nor->dev, nor->calib_size,
+ GFP_KERNEL);
+ nor->calib_buff = devm_kmalloc(nor->dev, nor->calib_size, GFP_KERNEL);
+ if (!nor->calib_ref_buff || !nor->calib_buff)
+ return -ENOMEM;
+
+ readval = nor->read(nor, nor->calib_off,
+ nor->calib_size, nor->calib_ref_buff);
+ if (readval < 0)
+ return readval;
+ else if (nor->calib_size != readval)
+ return -EIO;
+
+ if (!spi_nor_spimem_validate_calib_data(nor->calib_ref_buff,
+ nor->calib_size)) {
+ devm_kfree(nor->dev, nor->calib_ref_buff);
+ devm_kfree(nor->dev, nor->calib_buff);
+ nor->calib_ref_buff = NULL;
+ nor->calib_buff = NULL;
+ dev_warn(nor->dev,
+ "Skipping SPI-nor calibration: Calibration data has poor entropy.\n");
+ }
+
+ /* Restore settings. */
+ nor->addr_width = old_addr_width;
+
+ return 0;
+}
+#endif
+
static int spi_nor_default_setup(struct spi_nor *nor,
const struct flash_info *info,
const struct spi_nor_flash_parameter *params)
@@ -3240,6 +3386,12 @@ static int spi_nor_default_setup(struct spi_nor *nor,
spi_nor_adjust_hwcaps(nor, params, &shared_mask);
+#if CONFIG_IS_ENABLED(SPI_FLASH_HS_CALIB)
+ err = spi_nor_spimem_calib_init(nor, params, shared_mask);
+ if (err)
+ return err;
+#endif
+
/* Select the (Fast) Read command. */
err = spi_nor_select_read(nor, params, shared_mask);
if (err) {
@@ -3892,6 +4044,14 @@ static int spi_nor_init(struct spi_nor *nor)
set_4byte(nor, nor->info, 1);
}
+#if CONFIG_IS_ENABLED(SPI_FLASH_HS_CALIB)
+ err = spi_nor_spimem_calib(nor);
+ if (err) {
+ dev_err(nor->dev, "SPI calibration failed\n");
+ return err;
+ }
+#endif
+
return 0;
}
@@ -4057,6 +4217,14 @@ int spi_nor_scan(struct spi_nor *nor)
spi_nor_soft_reset(nor);
#endif /* CONFIG_SPI_FLASH_SOFT_RESET_ON_BOOT */
+#if CONFIG_IS_ENABLED(SPI_FLASH_HS_CALIB)
+ if (spi_mem_has_calibrate(nor->spi)) {
+ ret = spi_mem_calibrate(nor->spi, NULL);
+ if (ret)
+ return ret;
+ }
+#endif
+
info = spi_nor_read_id(nor);
if (IS_ERR_OR_NULL(info))
return -ENOENT;
@@ -763,6 +763,30 @@ ssize_t spi_mem_dirmap_write(struct spi_mem_dirmap_desc *desc,
}
EXPORT_SYMBOL_GPL(spi_mem_dirmap_write);
+#if CONFIG_IS_ENABLED(SPI_FLASH_HS_CALIB)
+int spi_mem_has_calibrate(struct spi_slave *slave)
+{
+ struct udevice *bus = slave->dev->parent;
+ struct dm_spi_ops *ops = spi_get_ops(bus);
+
+ return (ops->mem_ops && ops->mem_ops->calibrate);
+}
+EXPORT_SYMBOL_GPL(spi_mem_has_calibrate);
+
+int spi_mem_calibrate(struct spi_slave *slave,
+ int (*calib_chk_fn)(struct spi_slave *))
+{
+ struct udevice *bus = slave->dev->parent;
+ struct dm_spi_ops *ops = spi_get_ops(bus);
+
+ if (spi_mem_has_calibrate(slave))
+ return ops->mem_ops->calibrate(slave, calib_chk_fn);
+
+ return -ENOSYS;
+}
+EXPORT_SYMBOL_GPL(spi_mem_calibrate);
+#endif
+
#ifndef __UBOOT__
static inline struct spi_mem_driver *to_spi_mem_drv(struct device_driver *drv)
{
@@ -593,6 +593,13 @@ struct spi_nor {
u32 size;
u32 sector_size;
u32 erase_size;
+
+#if CONFIG_IS_ENABLED(SPI_FLASH_HS_CALIB)
+ u8 *calib_ref_buff;
+ u8 *calib_buff;
+ u32 calib_size;
+ u32 calib_off;
+#endif
};
#ifndef __UBOOT__
@@ -243,6 +243,15 @@ static inline void *spi_mem_get_drvdata(struct spi_mem *mem)
* the currently mapped area), and the caller of
* spi_mem_dirmap_write() is responsible for calling it again in
* this case.
+ * @calibrate: instruct driver to perform or clear high speed calibration for
+ * the memory device. The provided function should be used by the
+ * driver to check if a calibration configuration is valid. May be
+ * called multiple times as needed. Do note that calling the check
+ * function may call back into the other ops in this interface.
+ * A null calib_chk_fn should clear any existing calibration.
+ * calib_chk_fn is expected to return 0 on success, negative error
+ * codes, and a positive value on failure.
+ * The same return value expectation for the calibrate function.
*
* This interface should be implemented by SPI controllers providing an
* high-level interface to execute SPI memory operation, which is usually the
@@ -266,6 +275,10 @@ struct spi_controller_mem_ops {
u64 offs, size_t len, void *buf);
ssize_t (*dirmap_write)(struct spi_mem_dirmap_desc *desc,
u64 offs, size_t len, const void *buf);
+#if CONFIG_IS_ENABLED(SPI_FLASH_HS_CALIB)
+ int (*calibrate)(struct spi_slave *slave,
+ int (*calib_chk_fn)(struct spi_slave *));
+#endif
};
#ifndef __UBOOT__
@@ -341,6 +354,12 @@ ssize_t spi_mem_dirmap_read(struct spi_mem_dirmap_desc *desc,
ssize_t spi_mem_dirmap_write(struct spi_mem_dirmap_desc *desc,
u64 offs, size_t len, const void *buf);
+#if CONFIG_IS_ENABLED(SPI_FLASH_HS_CALIB)
+int spi_mem_has_calibrate(struct spi_slave *slave);
+int spi_mem_calibrate(struct spi_slave *slave,
+ int (*calib_chk_fn)(struct spi_slave *));
+#endif
+
#ifndef __UBOOT__
int spi_mem_driver_register_with_owner(struct spi_mem_driver *drv,
struct module *owner);