summaryrefslogtreecommitdiffstats
path: root/drivers
diff options
context:
space:
mode:
authorCédric Le Goater <clg@kaod.org>2022-05-09 19:56:10 +0200
committerMark Brown <broonie@kernel.org>2022-05-16 12:59:17 +0100
commitbb084f94e1bca4a5c4f689d7aa9b410220c1ed71 (patch)
treec7d06de8ba59dfca274914492491329da0c4b555 /drivers
parent9da06d7bdec7dad8018c23b180e410ef2e7a4367 (diff)
downloadlinux-bb084f94e1bca4a5c4f689d7aa9b410220c1ed71.tar.bz2
spi: aspeed: Adjust direct mapping to device size
The segment registers of the FMC/SPI controllers provide a way to configure the mapping window of the flash device contents on the AHB bus. Adjust this window to the size of the spi-mem mapping. Things get more complex with multiple devices. The driver needs to also adjust the window of the next device to make sure that there is no overlap, even if there is no available device. The proposal below is not perfect but it is covering all the cases we have seen on different boards with one and two devices on the same bus. Reviewed-by: Joel Stanley <joel@jms.id.au> Tested-by: Joel Stanley <joel@jms.id.au> Tested-by: Tao Ren <rentao.bupt@gmail.com> Tested-by: Jae Hyun Yoo <quic_jaehyoo@quicinc.com> Signed-off-by: Cédric Le Goater <clg@kaod.org> Link: https://lore.kernel.org/r/20220509175616.1089346-6-clg@kaod.org Signed-off-by: Mark Brown <broonie@kernel.org>
Diffstat (limited to 'drivers')
-rw-r--r--drivers/spi/spi-aspeed-smc.c88
1 files changed, 88 insertions, 0 deletions
diff --git a/drivers/spi/spi-aspeed-smc.c b/drivers/spi/spi-aspeed-smc.c
index 50cc7bd7ba3e..0aff42e20b8d 100644
--- a/drivers/spi/spi-aspeed-smc.c
+++ b/drivers/spi/spi-aspeed-smc.c
@@ -411,6 +411,92 @@ static int aspeed_spi_chip_set_default_window(struct aspeed_spi_chip *chip)
return chip->ahb_window_size ? 0 : -1;
}
+static int aspeed_spi_set_window(struct aspeed_spi *aspi,
+ const struct aspeed_spi_window *win)
+{
+ u32 start = aspi->ahb_base_phy + win->offset;
+ u32 end = start + win->size;
+ void __iomem *seg_reg = aspi->regs + CE0_SEGMENT_ADDR_REG + win->cs * 4;
+ u32 seg_val_backup = readl(seg_reg);
+ u32 seg_val = aspi->data->segment_reg(aspi, start, end);
+
+ if (seg_val == seg_val_backup)
+ return 0;
+
+ writel(seg_val, seg_reg);
+
+ /*
+ * Restore initial value if something goes wrong else we could
+ * loose access to the chip.
+ */
+ if (seg_val != readl(seg_reg)) {
+ dev_err(aspi->dev, "CE%d invalid window [ 0x%.8x - 0x%.8x ] %dMB",
+ win->cs, start, end - 1, win->size >> 20);
+ writel(seg_val_backup, seg_reg);
+ return -EIO;
+ }
+
+ if (win->size)
+ dev_dbg(aspi->dev, "CE%d new window [ 0x%.8x - 0x%.8x ] %dMB",
+ win->cs, start, end - 1, win->size >> 20);
+ else
+ dev_dbg(aspi->dev, "CE%d window closed", win->cs);
+
+ return 0;
+}
+
+/*
+ * Yet to be done when possible :
+ * - Align mappings on flash size (we don't have the info)
+ * - ioremap each window, not strictly necessary since the overall window
+ * is correct.
+ */
+static int aspeed_spi_chip_adjust_window(struct aspeed_spi_chip *chip,
+ u32 local_offset, u32 size)
+{
+ struct aspeed_spi *aspi = chip->aspi;
+ struct aspeed_spi_window windows[ASPEED_SPI_MAX_NUM_CS] = { 0 };
+ struct aspeed_spi_window *win = &windows[chip->cs];
+ int ret;
+
+ aspeed_spi_get_windows(aspi, windows);
+
+ /* Adjust this chip window */
+ win->offset += local_offset;
+ win->size = size;
+
+ if (win->offset + win->size > aspi->ahb_window_size) {
+ win->size = aspi->ahb_window_size - win->offset;
+ dev_warn(aspi->dev, "CE%d window resized to %dMB", chip->cs, win->size >> 20);
+ }
+
+ ret = aspeed_spi_set_window(aspi, win);
+ if (ret)
+ return ret;
+
+ /* Update chip mapping info */
+ chip->ahb_base = aspi->ahb_base + win->offset;
+ chip->ahb_window_size = win->size;
+
+ /*
+ * Also adjust next chip window to make sure that it does not
+ * overlap with the current window.
+ */
+ if (chip->cs < aspi->data->max_cs - 1) {
+ struct aspeed_spi_window *next = &windows[chip->cs + 1];
+
+ /* Change offset and size to keep the same end address */
+ if ((next->offset + next->size) > (win->offset + win->size))
+ next->size = (next->offset + next->size) - (win->offset + win->size);
+ else
+ next->size = 0;
+ next->offset = win->offset + win->size;
+
+ aspeed_spi_set_window(aspi, next);
+ }
+ return 0;
+}
+
static int aspeed_spi_dirmap_create(struct spi_mem_dirmap_desc *desc)
{
struct aspeed_spi *aspi = spi_controller_get_devdata(desc->mem->spi->master);
@@ -425,6 +511,8 @@ static int aspeed_spi_dirmap_create(struct spi_mem_dirmap_desc *desc)
if (op->data.dir != SPI_MEM_DATA_IN)
return -EOPNOTSUPP;
+ aspeed_spi_chip_adjust_window(chip, desc->info.offset, desc->info.length);
+
if (desc->info.length > chip->ahb_window_size)
dev_warn(aspi->dev, "CE%d window (%dMB) too small for mapping",
chip->cs, chip->ahb_window_size >> 20);