diff options
Diffstat (limited to 'drivers')
-rw-r--r-- | drivers/spi/spi-aspeed-smc.c | 88 |
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); |