spi: aspeed: Improve clock, timing and address

Merge series from Chin-Ting Kuo <chin-ting_kuo@aspeedtech.com>:

This patch series introduces several improvements to the
ASPEED SPI driver, targeting better stability, compatibility
and, flexibility across multiple ASPEED platforms.

Key changes include:

* Clock selection strategy update
  Improves fallback logic when timing calibration is skipped or
  fails, ensuring reliable boot behavior.

* Timing calibration enhancement for AST2600
  Replaces the previous "first-pass" strategy with a more robust
  algorithm that selects the optimal timing point.

* Default address decoding assignment
  Ensures each chip select (CS) has a valid decoding range during
  probe, avoiding detection failures due to missing or incorrect
  bootloader setup.

* Centralized address decoding management
  Refactors the decoding logic to centrally assign address windows,
  preventing improper trimming and improving layout flexibility.

* Per-platform decoding adjustment
  Introduces platform-specific `adjust_window` callbacks to handle
  platform specific hardware constraints for address decoding range.

* Selective memory mapping
  Optimizes memory usage by mapping only the required address window
  per CS to avoid exhaustion.
This commit is contained in:
Mark Brown 2025-10-15 11:32:54 +01:00
commit 7d9c2924f6
No known key found for this signature in database
GPG Key ID: 24D68B725D5487D0
1 changed files with 493 additions and 159 deletions

View File

@ -7,6 +7,7 @@
*/ */
#include <linux/clk.h> #include <linux/clk.h>
#include <linux/io.h>
#include <linux/module.h> #include <linux/module.h>
#include <linux/of.h> #include <linux/of.h>
#include <linux/of_platform.h> #include <linux/of_platform.h>
@ -67,6 +68,7 @@ struct aspeed_spi_chip {
u32 ahb_window_size; u32 ahb_window_size;
u32 ctl_val[ASPEED_SPI_MAX]; u32 ctl_val[ASPEED_SPI_MAX];
u32 clk_freq; u32 clk_freq;
bool force_user_mode;
}; };
struct aspeed_spi_data { struct aspeed_spi_data {
@ -78,10 +80,13 @@ struct aspeed_spi_data {
u32 timing; u32 timing;
u32 hclk_mask; u32 hclk_mask;
u32 hdiv_max; u32 hdiv_max;
u32 min_window_size;
u32 (*segment_start)(struct aspeed_spi *aspi, u32 reg); u32 (*segment_start)(struct aspeed_spi *aspi, u32 reg);
u32 (*segment_end)(struct aspeed_spi *aspi, u32 reg); u32 (*segment_end)(struct aspeed_spi *aspi, u32 reg);
u32 (*segment_reg)(struct aspeed_spi *aspi, u32 start, u32 end); u32 (*segment_reg)(struct aspeed_spi *aspi, u32 start, u32 end);
int (*adjust_window)(struct aspeed_spi *aspi);
u32 (*get_clk_div)(struct aspeed_spi_chip *chip, u32 hz);
int (*calibrate)(struct aspeed_spi_chip *chip, u32 hdiv, int (*calibrate)(struct aspeed_spi_chip *chip, u32 hdiv,
const u8 *golden_buf, u8 *test_buf); const u8 *golden_buf, u8 *test_buf);
}; };
@ -92,9 +97,9 @@ struct aspeed_spi {
const struct aspeed_spi_data *data; const struct aspeed_spi_data *data;
void __iomem *regs; void __iomem *regs;
void __iomem *ahb_base;
u32 ahb_base_phy; u32 ahb_base_phy;
u32 ahb_window_size; u32 ahb_window_size;
u32 num_cs;
struct device *dev; struct device *dev;
struct clk *clk; struct clk *clk;
@ -376,89 +381,270 @@ static const char *aspeed_spi_get_name(struct spi_mem *mem)
spi_get_chipselect(mem->spi, 0)); spi_get_chipselect(mem->spi, 0));
} }
struct aspeed_spi_window { static int aspeed_spi_set_window(struct aspeed_spi *aspi)
u32 cs;
u32 offset;
u32 size;
};
static void aspeed_spi_get_windows(struct aspeed_spi *aspi,
struct aspeed_spi_window windows[ASPEED_SPI_MAX_NUM_CS])
{ {
const struct aspeed_spi_data *data = aspi->data; struct device *dev = aspi->dev;
u32 reg_val; off_t offset = 0;
phys_addr_t start;
phys_addr_t end;
void __iomem *seg_reg_base = aspi->regs + CE0_SEGMENT_ADDR_REG;
void __iomem *seg_reg;
u32 seg_val_backup;
u32 seg_val;
u32 cs; u32 cs;
size_t window_size;
for (cs = 0; cs < aspi->data->max_cs; cs++) { for (cs = 0; cs < aspi->data->max_cs; cs++) {
reg_val = readl(aspi->regs + CE0_SEGMENT_ADDR_REG + cs * 4); if (aspi->chips[cs].ahb_base) {
windows[cs].cs = cs; iounmap(aspi->chips[cs].ahb_base);
windows[cs].size = data->segment_end(aspi, reg_val) - aspi->chips[cs].ahb_base = NULL;
data->segment_start(aspi, reg_val); }
windows[cs].offset = data->segment_start(aspi, reg_val) - aspi->ahb_base_phy;
dev_vdbg(aspi->dev, "CE%d offset=0x%.8x size=0x%x\n", cs,
windows[cs].offset, windows[cs].size);
} }
for (cs = 0; cs < aspi->data->max_cs; cs++) {
seg_reg = seg_reg_base + cs * 4;
seg_val_backup = readl(seg_reg);
start = aspi->ahb_base_phy + offset;
window_size = aspi->chips[cs].ahb_window_size;
end = start + window_size;
seg_val = aspi->data->segment_reg(aspi, start, end);
writel(seg_val, seg_reg);
/*
* Restore initial value if something goes wrong or the segment
* register is written protected.
*/
if (seg_val != readl(seg_reg)) {
dev_warn(dev, "CE%d expected window [ 0x%.9llx - 0x%.9llx ] %zdMB\n",
cs, (u64)start, (u64)end - 1, window_size >> 20);
writel(seg_val_backup, seg_reg);
window_size = aspi->data->segment_end(aspi, seg_val_backup) -
aspi->data->segment_start(aspi, seg_val_backup);
aspi->chips[cs].ahb_window_size = window_size;
end = start + window_size;
}
if (window_size != 0)
dev_dbg(dev, "CE%d window [ 0x%.9llx - 0x%.9llx ] %zdMB\n",
cs, (u64)start, (u64)end - 1, window_size >> 20);
else
dev_dbg(dev, "CE%d window closed\n", cs);
offset += window_size;
if (offset > aspi->ahb_window_size) {
dev_err(dev, "CE%d offset value 0x%llx is too large.\n",
cs, (u64)offset);
return -ENOSPC;
}
/*
* No need to map the address deocding range when
* - window size is 0.
* - the CS is unused.
*/
if (window_size == 0 || cs >= aspi->num_cs)
continue;
aspi->chips[cs].ahb_base =
devm_ioremap(aspi->dev, start, window_size);
if (!aspi->chips[cs].ahb_base) {
dev_err(aspi->dev,
"Fail to remap window [0x%.9llx - 0x%.9llx]\n",
(u64)start, (u64)end - 1);
return -ENOMEM;
}
}
return 0;
} }
/* static const struct aspeed_spi_data ast2500_spi_data;
* On the AST2600, some CE windows are closed by default at reset but static const struct aspeed_spi_data ast2600_spi_data;
* U-Boot should open all. static const struct aspeed_spi_data ast2600_fmc_data;
*/
static int aspeed_spi_chip_set_default_window(struct aspeed_spi_chip *chip) static int aspeed_spi_chip_set_default_window(struct aspeed_spi *aspi)
{ {
struct aspeed_spi *aspi = chip->aspi; u32 cs;
struct aspeed_spi_window windows[ASPEED_SPI_MAX_NUM_CS] = { 0 };
struct aspeed_spi_window *win = &windows[chip->cs];
/* No segment registers for the AST2400 SPI controller */ /* No segment registers for the AST2400 SPI controller */
if (aspi->data == &ast2400_spi_data) { if (aspi->data == &ast2400_spi_data) {
win->offset = 0; aspi->chips[0].ahb_base = devm_ioremap(aspi->dev,
win->size = aspi->ahb_window_size; aspi->ahb_base_phy,
} else { aspi->ahb_window_size);
aspeed_spi_get_windows(aspi, windows); aspi->chips[0].ahb_window_size = aspi->ahb_window_size;
return 0;
} }
chip->ahb_base = aspi->ahb_base + win->offset; /* Assign the minimum window size to each CS */
chip->ahb_window_size = win->size; for (cs = 0; cs < aspi->num_cs; cs++) {
aspi->chips[cs].ahb_window_size = aspi->data->min_window_size;
dev_dbg(aspi->dev, "CE%d default window [ 0x%.8x - 0x%.8x ]",
cs, aspi->ahb_base_phy + aspi->data->min_window_size * cs,
aspi->ahb_base_phy + aspi->data->min_window_size * cs - 1);
}
dev_dbg(aspi->dev, "CE%d default window [ 0x%.8x - 0x%.8x ] %dMB", /* Close unused CS */
chip->cs, aspi->ahb_base_phy + win->offset, for (cs = aspi->num_cs; cs < aspi->data->max_cs; cs++)
aspi->ahb_base_phy + win->offset + win->size - 1, aspi->chips[cs].ahb_window_size = 0;
win->size >> 20);
return chip->ahb_window_size ? 0 : -1; if (aspi->data->adjust_window)
aspi->data->adjust_window(aspi);
return aspeed_spi_set_window(aspi);
} }
static int aspeed_spi_set_window(struct aspeed_spi *aspi, /*
const struct aspeed_spi_window *win) * As the flash size grows up, we need to trim some decoding
* size if needed for the sake of conforming the maximum
* decoding size. We trim the decoding size from the rear CS
* to avoid affecting the default boot up sequence, usually,
* from CS0. Notice, if a CS decoding size is trimmed,
* command mode may not work perfectly on that CS, but it only
* affect performance and the debug function.
*/
static int aspeed_spi_trim_window_size(struct aspeed_spi *aspi)
{ {
u32 start = aspi->ahb_base_phy + win->offset; struct aspeed_spi_chip *chips = aspi->chips;
u32 end = start + win->size; size_t total_sz;
void __iomem *seg_reg = aspi->regs + CE0_SEGMENT_ADDR_REG + win->cs * 4; int cs = aspi->data->max_cs - 1;
u32 seg_val_backup = readl(seg_reg); u32 i;
u32 seg_val = aspi->data->segment_reg(aspi, start, end); bool trimmed = false;
if (seg_val == seg_val_backup) do {
return 0; total_sz = 0;
for (i = 0; i < aspi->data->max_cs; i++)
total_sz += chips[i].ahb_window_size;
writel(seg_val, seg_reg); if (cs < 0)
return -ENOMEM;
/* if (chips[cs].ahb_window_size <= aspi->data->min_window_size) {
* Restore initial value if something goes wrong else we could cs--;
* loose access to the chip. continue;
*/ }
if (seg_val != readl(seg_reg)) {
dev_err(aspi->dev, "CE%d invalid window [ 0x%.8x - 0x%.8x ] %dMB", if (total_sz > aspi->ahb_window_size) {
win->cs, start, end - 1, win->size >> 20); chips[cs].ahb_window_size -=
writel(seg_val_backup, seg_reg); aspi->data->min_window_size;
return -EIO; total_sz -= aspi->data->min_window_size;
/*
* If the ahb window size is ever trimmed, only user
* mode can be adopted to access the whole flash.
*/
chips[cs].force_user_mode = true;
trimmed = true;
}
} while (total_sz > aspi->ahb_window_size);
if (trimmed) {
dev_warn(aspi->dev, "Window size after triming:\n");
for (cs = 0; cs < aspi->data->max_cs; cs++) {
dev_warn(aspi->dev, "CE%d: 0x%08x\n",
cs, chips[cs].ahb_window_size);
}
} }
if (win->size) return 0;
dev_dbg(aspi->dev, "CE%d new window [ 0x%.8x - 0x%.8x ] %dMB", }
win->cs, start, end - 1, win->size >> 20);
else static int aspeed_adjust_window_ast2400(struct aspeed_spi *aspi)
dev_dbg(aspi->dev, "CE%d window closed", win->cs); {
int ret;
int cs;
struct aspeed_spi_chip *chips = aspi->chips;
/* Close unused CS. */
for (cs = aspi->num_cs; cs < aspi->data->max_cs; cs++)
chips[cs].ahb_window_size = 0;
ret = aspeed_spi_trim_window_size(aspi);
if (ret != 0)
return ret;
return 0;
}
/*
* For AST2500, the minimum address decoding size for each CS
* is 8MB. This address decoding size is mandatory for each
* CS no matter whether it will be used. This is a HW limitation.
*/
static int aspeed_adjust_window_ast2500(struct aspeed_spi *aspi)
{
int ret;
int cs, i;
u32 cum_size, rem_size;
struct aspeed_spi_chip *chips = aspi->chips;
/* Assign min_window_sz to unused CS. */
for (cs = aspi->num_cs; cs < aspi->data->max_cs; cs++) {
if (chips[cs].ahb_window_size < aspi->data->min_window_size)
chips[cs].ahb_window_size =
aspi->data->min_window_size;
}
/*
* If command mode or normal mode is used by dirmap read, the start
* address of a window should be multiple of its related flash size.
* Namely, the total windows size from flash 0 to flash N should
* be multiple of the size of flash (N + 1).
*/
for (cs = aspi->num_cs - 1; cs >= 0; cs--) {
cum_size = 0;
for (i = 0; i < cs; i++)
cum_size += chips[i].ahb_window_size;
rem_size = cum_size % chips[cs].ahb_window_size;
if (chips[cs].ahb_window_size != 0 && rem_size != 0)
chips[0].ahb_window_size +=
chips[cs].ahb_window_size - rem_size;
}
ret = aspeed_spi_trim_window_size(aspi);
if (ret != 0)
return ret;
/* The total window size of AST2500 SPI1 CS0 and CS1 must be 128MB */
if (aspi->data == &ast2500_spi_data)
chips[1].ahb_window_size =
0x08000000 - chips[0].ahb_window_size;
return 0;
}
static int aspeed_adjust_window_ast2600(struct aspeed_spi *aspi)
{
int ret;
int cs, i;
u32 cum_size, rem_size;
struct aspeed_spi_chip *chips = aspi->chips;
/* Close unused CS. */
for (cs = aspi->num_cs; cs < aspi->data->max_cs; cs++)
chips[cs].ahb_window_size = 0;
/*
* If command mode or normal mode is used by dirmap read, the start
* address of a window should be multiple of its related flash size.
* Namely, the total windows size from flash 0 to flash N should
* be multiple of the size of flash (N + 1).
*/
for (cs = aspi->num_cs - 1; cs >= 0; cs--) {
cum_size = 0;
for (i = 0; i < cs; i++)
cum_size += chips[i].ahb_window_size;
rem_size = cum_size % chips[cs].ahb_window_size;
if (chips[cs].ahb_window_size != 0 && rem_size != 0)
chips[0].ahb_window_size +=
chips[cs].ahb_window_size - rem_size;
}
ret = aspeed_spi_trim_window_size(aspi);
if (ret != 0)
return ret;
return 0; return 0;
} }
@ -469,78 +655,27 @@ static int aspeed_spi_set_window(struct aspeed_spi *aspi,
* - ioremap each window, not strictly necessary since the overall window * - ioremap each window, not strictly necessary since the overall window
* is correct. * is correct.
*/ */
static const struct aspeed_spi_data ast2500_spi_data;
static const struct aspeed_spi_data ast2600_spi_data;
static const struct aspeed_spi_data ast2600_fmc_data;
static int aspeed_spi_chip_adjust_window(struct aspeed_spi_chip *chip, static int aspeed_spi_chip_adjust_window(struct aspeed_spi_chip *chip,
u32 local_offset, u32 size) u32 local_offset, u32 size)
{ {
struct aspeed_spi *aspi = chip->aspi; 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; int ret;
/* No segment registers for the AST2400 SPI controller */ /* No segment registers for the AST2400 SPI controller */
if (aspi->data == &ast2400_spi_data) if (aspi->data == &ast2400_spi_data)
return 0; return 0;
/*
* Due to an HW issue on the AST2500 SPI controller, the CE0
* window size should be smaller than the maximum 128MB.
*/
if (aspi->data == &ast2500_spi_data && chip->cs == 0 && size == SZ_128M) {
size = 120 << 20;
dev_info(aspi->dev, "CE%d window resized to %dMB (AST2500 HW quirk)",
chip->cs, size >> 20);
}
/*
* The decoding size of AST2600 SPI controller should set at
* least 2MB.
*/
if ((aspi->data == &ast2600_spi_data || aspi->data == &ast2600_fmc_data) &&
size < SZ_2M) {
size = SZ_2M;
dev_info(aspi->dev, "CE%d window resized to %dMB (AST2600 Decoding)",
chip->cs, size >> 20);
}
aspeed_spi_get_windows(aspi, windows);
/* Adjust this chip window */ /* Adjust this chip window */
win->offset += local_offset; aspi->chips[chip->cs].ahb_window_size = size;
win->size = size;
if (win->offset + win->size > aspi->ahb_window_size) { /* Adjust the overall windows size regarding each platform */
win->size = aspi->ahb_window_size - win->offset; if (aspi->data->adjust_window)
dev_warn(aspi->dev, "CE%d window resized to %dMB", chip->cs, win->size >> 20); aspi->data->adjust_window(aspi);
}
ret = aspeed_spi_set_window(aspi, win); ret = aspeed_spi_set_window(aspi);
if (ret) if (ret)
return 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; return 0;
} }
@ -619,7 +754,7 @@ static ssize_t aspeed_spi_dirmap_read(struct spi_mem_dirmap_desc *desc,
struct aspeed_spi_chip *chip = &aspi->chips[spi_get_chipselect(desc->mem->spi, 0)]; struct aspeed_spi_chip *chip = &aspi->chips[spi_get_chipselect(desc->mem->spi, 0)];
/* Switch to USER command mode if mapping window is too small */ /* Switch to USER command mode if mapping window is too small */
if (chip->ahb_window_size < offset + len) { if (chip->ahb_window_size < offset + len || chip->force_user_mode) {
int ret; int ret;
ret = aspeed_spi_read_user(chip, &desc->info.op_tmpl, offset, len, buf); ret = aspeed_spi_read_user(chip, &desc->info.op_tmpl, offset, len, buf);
@ -677,11 +812,6 @@ static int aspeed_spi_setup(struct spi_device *spi)
if (data->hastype) if (data->hastype)
aspeed_spi_chip_set_type(aspi, cs, CONFIG_TYPE_SPI); aspeed_spi_chip_set_type(aspi, cs, CONFIG_TYPE_SPI);
if (aspeed_spi_chip_set_default_window(chip) < 0) {
dev_warn(aspi->dev, "CE%d window invalid", cs);
return -EINVAL;
}
aspeed_spi_chip_enable(aspi, cs, true); aspeed_spi_chip_enable(aspi, cs, true);
chip->ctl_val[ASPEED_SPI_BASE] = CTRL_CE_STOP_ACTIVE | CTRL_IO_MODE_USER; chip->ctl_val[ASPEED_SPI_BASE] = CTRL_CE_STOP_ACTIVE | CTRL_IO_MODE_USER;
@ -734,10 +864,10 @@ static int aspeed_spi_probe(struct platform_device *pdev)
if (IS_ERR(aspi->regs)) if (IS_ERR(aspi->regs))
return PTR_ERR(aspi->regs); return PTR_ERR(aspi->regs);
aspi->ahb_base = devm_platform_get_and_ioremap_resource(pdev, 1, &res); res = platform_get_resource(pdev, IORESOURCE_MEM, 1);
if (IS_ERR(aspi->ahb_base)) { if (IS_ERR(res)) {
dev_err(dev, "missing AHB mapping window\n"); dev_err(dev, "missing AHB memory\n");
return PTR_ERR(aspi->ahb_base); return PTR_ERR(res);
} }
aspi->ahb_window_size = resource_size(res); aspi->ahb_window_size = resource_size(res);
@ -762,9 +892,17 @@ static int aspeed_spi_probe(struct platform_device *pdev)
ctlr->mem_ops = &aspeed_spi_mem_ops; ctlr->mem_ops = &aspeed_spi_mem_ops;
ctlr->setup = aspeed_spi_setup; ctlr->setup = aspeed_spi_setup;
ctlr->cleanup = aspeed_spi_cleanup; ctlr->cleanup = aspeed_spi_cleanup;
ctlr->num_chipselect = data->max_cs; ctlr->num_chipselect = of_get_available_child_count(dev->of_node);
ctlr->dev.of_node = dev->of_node; ctlr->dev.of_node = dev->of_node;
aspi->num_cs = ctlr->num_chipselect;
ret = aspeed_spi_chip_set_default_window(aspi);
if (ret) {
dev_err(&pdev->dev, "fail to set default window\n");
return ret;
}
ret = devm_spi_register_controller(dev, ctlr); ret = devm_spi_register_controller(dev, ctlr);
if (ret) if (ret)
dev_err(&pdev->dev, "spi_register_controller failed\n"); dev_err(&pdev->dev, "spi_register_controller failed\n");
@ -942,26 +1080,149 @@ static bool aspeed_spi_check_calib_data(const u8 *test_buf, u32 size)
} }
static const u32 aspeed_spi_hclk_divs[] = { static const u32 aspeed_spi_hclk_divs[] = {
0xf, /* HCLK */ /* HCLK, HCLK/2, HCLK/3, HCLK/4, HCLK/5, ..., HCLK/16 */
0x7, /* HCLK/2 */ 0xf, 0x7, 0xe, 0x6, 0xd,
0xe, /* HCLK/3 */ 0x5, 0xc, 0x4, 0xb, 0x3,
0x6, /* HCLK/4 */ 0xa, 0x2, 0x9, 0x1, 0x8,
0xd, /* HCLK/5 */ 0x0
}; };
#define ASPEED_SPI_HCLK_DIV(i) \ #define ASPEED_SPI_HCLK_DIV(i) \
(aspeed_spi_hclk_divs[(i) - 1] << CTRL_FREQ_SEL_SHIFT) (aspeed_spi_hclk_divs[(i) - 1] << CTRL_FREQ_SEL_SHIFT)
/* Transfer maximum clock frequency to register setting */
static u32 aspeed_get_clk_div_ast2400(struct aspeed_spi_chip *chip,
u32 max_hz)
{
struct device *dev = chip->aspi->dev;
u32 hclk_clk = chip->aspi->clk_freq;
u32 div_ctl = 0;
u32 i;
bool found = false;
/* FMC/SPIR10[11:8] */
for (i = 1; i <= ARRAY_SIZE(aspeed_spi_hclk_divs); i++) {
if (hclk_clk / i <= max_hz) {
found = true;
break;
}
}
if (found) {
div_ctl = ASPEED_SPI_HCLK_DIV(i);
chip->clk_freq = hclk_clk / i;
}
dev_dbg(dev, "found: %s, hclk: %d, max_clk: %d\n",
found ? "yes" : "no", hclk_clk, max_hz);
if (found) {
dev_dbg(dev, "h_div: 0x%08x, speed: %d\n",
div_ctl, chip->clk_freq);
}
return div_ctl;
}
static u32 aspeed_get_clk_div_ast2500(struct aspeed_spi_chip *chip,
u32 max_hz)
{
struct device *dev = chip->aspi->dev;
u32 hclk_clk = chip->aspi->clk_freq;
u32 div_ctl = 0;
u32 i;
bool found = false;
/* FMC/SPIR10[11:8] */
for (i = 1; i <= ARRAY_SIZE(aspeed_spi_hclk_divs); i++) {
if (hclk_clk / i <= max_hz) {
found = true;
chip->clk_freq = hclk_clk / i;
break;
}
}
if (found) {
div_ctl = ASPEED_SPI_HCLK_DIV(i);
goto end;
}
for (i = 1; i <= ARRAY_SIZE(aspeed_spi_hclk_divs); i++) {
if (hclk_clk / (i * 4) <= max_hz) {
found = true;
chip->clk_freq = hclk_clk / (i * 4);
break;
}
}
if (found)
div_ctl = BIT(13) | ASPEED_SPI_HCLK_DIV(i);
end:
dev_dbg(dev, "found: %s, hclk: %d, max_clk: %d\n",
found ? "yes" : "no", hclk_clk, max_hz);
if (found) {
dev_dbg(dev, "h_div: 0x%08x, speed: %d\n",
div_ctl, chip->clk_freq);
}
return div_ctl;
}
static u32 aspeed_get_clk_div_ast2600(struct aspeed_spi_chip *chip,
u32 max_hz)
{
struct device *dev = chip->aspi->dev;
u32 hclk_clk = chip->aspi->clk_freq;
u32 div_ctl = 0;
u32 i, j;
bool found = false;
/* FMC/SPIR10[27:24] */
for (j = 0; j < 16; j++) {
/* FMC/SPIR10[11:8] */
for (i = 1; i <= ARRAY_SIZE(aspeed_spi_hclk_divs); i++) {
if (j == 0 && i == 1)
continue;
if (hclk_clk / (j * 16 + i) <= max_hz) {
found = true;
break;
}
}
if (found) {
div_ctl = ((j << 24) | ASPEED_SPI_HCLK_DIV(i));
chip->clk_freq = hclk_clk / (j * 16 + i);
break;
}
}
dev_dbg(dev, "found: %s, hclk: %d, max_clk: %d\n",
found ? "yes" : "no", hclk_clk, max_hz);
if (found) {
dev_dbg(dev, "h_div: 0x%08x, speed: %d\n",
div_ctl, chip->clk_freq);
}
return div_ctl;
}
static int aspeed_spi_do_calibration(struct aspeed_spi_chip *chip) static int aspeed_spi_do_calibration(struct aspeed_spi_chip *chip)
{ {
struct aspeed_spi *aspi = chip->aspi; struct aspeed_spi *aspi = chip->aspi;
const struct aspeed_spi_data *data = aspi->data; const struct aspeed_spi_data *data = aspi->data;
u32 ahb_freq = aspi->clk_freq; u32 ahb_freq = aspi->clk_freq;
u32 max_freq = chip->clk_freq; u32 max_freq = chip->clk_freq;
bool exec_calib = false;
u32 best_freq = 0;
u32 ctl_val; u32 ctl_val;
u8 *golden_buf = NULL; u8 *golden_buf = NULL;
u8 *test_buf = NULL; u8 *test_buf = NULL;
int i, rc, best_div = -1; int i, rc;
u32 div_ctl;
dev_dbg(aspi->dev, "calculate timing compensation - AHB freq: %d MHz", dev_dbg(aspi->dev, "calculate timing compensation - AHB freq: %d MHz",
ahb_freq / 1000000); ahb_freq / 1000000);
@ -982,7 +1243,7 @@ static int aspeed_spi_do_calibration(struct aspeed_spi_chip *chip)
memcpy_fromio(golden_buf, chip->ahb_base, CALIBRATE_BUF_SIZE); memcpy_fromio(golden_buf, chip->ahb_base, CALIBRATE_BUF_SIZE);
if (!aspeed_spi_check_calib_data(golden_buf, CALIBRATE_BUF_SIZE)) { if (!aspeed_spi_check_calib_data(golden_buf, CALIBRATE_BUF_SIZE)) {
dev_info(aspi->dev, "Calibration area too uniform, using low speed"); dev_info(aspi->dev, "Calibration area too uniform, using low speed");
goto no_calib; goto end_calib;
} }
#if defined(VERBOSE_DEBUG) #if defined(VERBOSE_DEBUG)
@ -991,7 +1252,7 @@ static int aspeed_spi_do_calibration(struct aspeed_spi_chip *chip)
#endif #endif
/* Now we iterate the HCLK dividers until we find our breaking point */ /* Now we iterate the HCLK dividers until we find our breaking point */
for (i = ARRAY_SIZE(aspeed_spi_hclk_divs); i > data->hdiv_max - 1; i--) { for (i = 5; i > data->hdiv_max - 1; i--) {
u32 tv, freq; u32 tv, freq;
freq = ahb_freq / i; freq = ahb_freq / i;
@ -1004,22 +1265,33 @@ static int aspeed_spi_do_calibration(struct aspeed_spi_chip *chip)
dev_dbg(aspi->dev, "Trying HCLK/%d [%08x] ...", i, tv); dev_dbg(aspi->dev, "Trying HCLK/%d [%08x] ...", i, tv);
rc = data->calibrate(chip, i, golden_buf, test_buf); rc = data->calibrate(chip, i, golden_buf, test_buf);
if (rc == 0) if (rc == 0)
best_div = i; best_freq = freq;
exec_calib = true;
} }
/* Nothing found ? */ end_calib:
if (best_div < 0) { if (!exec_calib) {
dev_warn(aspi->dev, "No good frequency, using dumb slow"); /* calibration process is not executed */
dev_warn(aspi->dev, "Force to dts configuration %dkHz.\n",
max_freq / 1000);
div_ctl = data->get_clk_div(chip, max_freq);
} else if (best_freq == 0) {
/* calibration process is executed, but no good frequency */
dev_warn(aspi->dev, "No good frequency, using dumb slow\n");
div_ctl = 0;
} else { } else {
dev_dbg(aspi->dev, "Found good read timings at HCLK/%d", best_div); dev_dbg(aspi->dev, "Found good read timings at %dMHz.\n",
best_freq / 1000000);
/* Record the freq */ div_ctl = data->get_clk_div(chip, best_freq);
for (i = 0; i < ASPEED_SPI_MAX; i++) }
chip->ctl_val[i] = (chip->ctl_val[i] & data->hclk_mask) |
ASPEED_SPI_HCLK_DIV(best_div); /* Record the freq */
for (i = 0; i < ASPEED_SPI_MAX; i++) {
chip->ctl_val[i] = (chip->ctl_val[i] & data->hclk_mask) |
div_ctl;
} }
no_calib:
writel(chip->ctl_val[ASPEED_SPI_READ], chip->ctl); writel(chip->ctl_val[ASPEED_SPI_READ], chip->ctl);
kfree(test_buf); kfree(test_buf);
return 0; return 0;
@ -1027,21 +1299,57 @@ static int aspeed_spi_do_calibration(struct aspeed_spi_chip *chip)
#define TIMING_DELAY_DI BIT(3) #define TIMING_DELAY_DI BIT(3)
#define TIMING_DELAY_HCYCLE_MAX 5 #define TIMING_DELAY_HCYCLE_MAX 5
#define TIMING_DELAY_INPUT_MAX 16
#define TIMING_REG_AST2600(chip) \ #define TIMING_REG_AST2600(chip) \
((chip)->aspi->regs + (chip)->aspi->data->timing + \ ((chip)->aspi->regs + (chip)->aspi->data->timing + \
(chip)->cs * 4) (chip)->cs * 4)
/*
* This function returns the center point of the longest
* continuous "pass" interval within the buffer. The interval
* must contains the highest number of consecutive "pass"
* results and not span across multiple rows.
*/
static u32 aspeed_spi_ast2600_optimized_timing(u32 rows, u32 cols,
u8 buf[rows][cols])
{
int r = 0, c = 0;
int max = 0;
int i, j;
for (i = 0; i < rows; i++) {
for (j = 0; j < cols;) {
int k = j;
while (k < cols && buf[i][k])
k++;
if (k - j > max) {
max = k - j;
r = i;
c = j + (k - j) / 2;
}
j = k + 1;
}
}
return max > 4 ? r * cols + c : 0;
}
static int aspeed_spi_ast2600_calibrate(struct aspeed_spi_chip *chip, u32 hdiv, static int aspeed_spi_ast2600_calibrate(struct aspeed_spi_chip *chip, u32 hdiv,
const u8 *golden_buf, u8 *test_buf) const u8 *golden_buf, u8 *test_buf)
{ {
struct aspeed_spi *aspi = chip->aspi; struct aspeed_spi *aspi = chip->aspi;
int hcycle; int hcycle;
int delay_ns;
u32 shift = (hdiv - 2) << 3; u32 shift = (hdiv - 2) << 3;
u32 mask = ~(0xfu << shift); u32 mask = ~(0xffu << shift);
u32 fread_timing_val = 0; u32 fread_timing_val = 0;
u8 calib_res[6][17] = {0};
u32 calib_point;
for (hcycle = 0; hcycle <= TIMING_DELAY_HCYCLE_MAX; hcycle++) { for (hcycle = 0; hcycle <= TIMING_DELAY_HCYCLE_MAX; hcycle++) {
int delay_ns;
bool pass = false; bool pass = false;
fread_timing_val &= mask; fread_timing_val &= mask;
@ -1054,14 +1362,14 @@ static int aspeed_spi_ast2600_calibrate(struct aspeed_spi_chip *chip, u32 hdiv,
" * [%08x] %d HCLK delay, DI delay none : %s", " * [%08x] %d HCLK delay, DI delay none : %s",
fread_timing_val, hcycle, pass ? "PASS" : "FAIL"); fread_timing_val, hcycle, pass ? "PASS" : "FAIL");
if (pass) if (pass)
return 0; calib_res[hcycle][0] = 1;
/* Add DI input delays */ /* Add DI input delays */
fread_timing_val &= mask; fread_timing_val &= mask;
fread_timing_val |= (TIMING_DELAY_DI | hcycle) << shift; fread_timing_val |= (TIMING_DELAY_DI | hcycle) << shift;
for (delay_ns = 0; delay_ns < 0x10; delay_ns++) { for (delay_ns = 0; delay_ns < TIMING_DELAY_INPUT_MAX; delay_ns++) {
fread_timing_val &= ~(0xf << (4 + shift)); fread_timing_val &= ~(0xfu << (4 + shift));
fread_timing_val |= delay_ns << (4 + shift); fread_timing_val |= delay_ns << (4 + shift);
writel(fread_timing_val, TIMING_REG_AST2600(chip)); writel(fread_timing_val, TIMING_REG_AST2600(chip));
@ -1070,18 +1378,28 @@ static int aspeed_spi_ast2600_calibrate(struct aspeed_spi_chip *chip, u32 hdiv,
" * [%08x] %d HCLK delay, DI delay %d.%dns : %s", " * [%08x] %d HCLK delay, DI delay %d.%dns : %s",
fread_timing_val, hcycle, (delay_ns + 1) / 2, fread_timing_val, hcycle, (delay_ns + 1) / 2,
(delay_ns + 1) & 1 ? 5 : 5, pass ? "PASS" : "FAIL"); (delay_ns + 1) & 1 ? 5 : 5, pass ? "PASS" : "FAIL");
/*
* TODO: This is optimistic. We should look
* for a working interval and save the middle
* value in the read timing register.
*/
if (pass) if (pass)
return 0; calib_res[hcycle][delay_ns + 1] = 1;
} }
} }
calib_point = aspeed_spi_ast2600_optimized_timing(6, 17, calib_res);
/* No good setting for this frequency */ /* No good setting for this frequency */
return -1; if (calib_point == 0)
return -1;
hcycle = calib_point / 17;
delay_ns = calib_point % 17;
fread_timing_val = (TIMING_DELAY_DI | hcycle | (delay_ns << 4)) << shift;
dev_dbg(aspi->dev, "timing val: %08x, final hcycle: %d, delay_ns: %d\n",
fread_timing_val, hcycle, delay_ns);
writel(fread_timing_val, TIMING_REG_AST2600(chip));
return 0;
} }
/* /*
@ -1095,10 +1413,13 @@ static const struct aspeed_spi_data ast2400_fmc_data = {
.timing = CE0_TIMING_COMPENSATION_REG, .timing = CE0_TIMING_COMPENSATION_REG,
.hclk_mask = 0xfffff0ff, .hclk_mask = 0xfffff0ff,
.hdiv_max = 1, .hdiv_max = 1,
.min_window_size = 0x800000,
.calibrate = aspeed_spi_calibrate, .calibrate = aspeed_spi_calibrate,
.get_clk_div = aspeed_get_clk_div_ast2400,
.segment_start = aspeed_spi_segment_start, .segment_start = aspeed_spi_segment_start,
.segment_end = aspeed_spi_segment_end, .segment_end = aspeed_spi_segment_end,
.segment_reg = aspeed_spi_segment_reg, .segment_reg = aspeed_spi_segment_reg,
.adjust_window = aspeed_adjust_window_ast2400,
}; };
static const struct aspeed_spi_data ast2400_spi_data = { static const struct aspeed_spi_data ast2400_spi_data = {
@ -1109,6 +1430,7 @@ static const struct aspeed_spi_data ast2400_spi_data = {
.timing = 0x14, .timing = 0x14,
.hclk_mask = 0xfffff0ff, .hclk_mask = 0xfffff0ff,
.hdiv_max = 1, .hdiv_max = 1,
.get_clk_div = aspeed_get_clk_div_ast2400,
.calibrate = aspeed_spi_calibrate, .calibrate = aspeed_spi_calibrate,
/* No segment registers */ /* No segment registers */
}; };
@ -1121,10 +1443,13 @@ static const struct aspeed_spi_data ast2500_fmc_data = {
.timing = CE0_TIMING_COMPENSATION_REG, .timing = CE0_TIMING_COMPENSATION_REG,
.hclk_mask = 0xffffd0ff, .hclk_mask = 0xffffd0ff,
.hdiv_max = 1, .hdiv_max = 1,
.min_window_size = 0x800000,
.get_clk_div = aspeed_get_clk_div_ast2500,
.calibrate = aspeed_spi_calibrate, .calibrate = aspeed_spi_calibrate,
.segment_start = aspeed_spi_segment_start, .segment_start = aspeed_spi_segment_start,
.segment_end = aspeed_spi_segment_end, .segment_end = aspeed_spi_segment_end,
.segment_reg = aspeed_spi_segment_reg, .segment_reg = aspeed_spi_segment_reg,
.adjust_window = aspeed_adjust_window_ast2500,
}; };
static const struct aspeed_spi_data ast2500_spi_data = { static const struct aspeed_spi_data ast2500_spi_data = {
@ -1135,10 +1460,13 @@ static const struct aspeed_spi_data ast2500_spi_data = {
.timing = CE0_TIMING_COMPENSATION_REG, .timing = CE0_TIMING_COMPENSATION_REG,
.hclk_mask = 0xffffd0ff, .hclk_mask = 0xffffd0ff,
.hdiv_max = 1, .hdiv_max = 1,
.min_window_size = 0x800000,
.get_clk_div = aspeed_get_clk_div_ast2500,
.calibrate = aspeed_spi_calibrate, .calibrate = aspeed_spi_calibrate,
.segment_start = aspeed_spi_segment_start, .segment_start = aspeed_spi_segment_start,
.segment_end = aspeed_spi_segment_end, .segment_end = aspeed_spi_segment_end,
.segment_reg = aspeed_spi_segment_reg, .segment_reg = aspeed_spi_segment_reg,
.adjust_window = aspeed_adjust_window_ast2500,
}; };
static const struct aspeed_spi_data ast2600_fmc_data = { static const struct aspeed_spi_data ast2600_fmc_data = {
@ -1150,10 +1478,13 @@ static const struct aspeed_spi_data ast2600_fmc_data = {
.timing = CE0_TIMING_COMPENSATION_REG, .timing = CE0_TIMING_COMPENSATION_REG,
.hclk_mask = 0xf0fff0ff, .hclk_mask = 0xf0fff0ff,
.hdiv_max = 2, .hdiv_max = 2,
.min_window_size = 0x200000,
.get_clk_div = aspeed_get_clk_div_ast2600,
.calibrate = aspeed_spi_ast2600_calibrate, .calibrate = aspeed_spi_ast2600_calibrate,
.segment_start = aspeed_spi_segment_ast2600_start, .segment_start = aspeed_spi_segment_ast2600_start,
.segment_end = aspeed_spi_segment_ast2600_end, .segment_end = aspeed_spi_segment_ast2600_end,
.segment_reg = aspeed_spi_segment_ast2600_reg, .segment_reg = aspeed_spi_segment_ast2600_reg,
.adjust_window = aspeed_adjust_window_ast2600,
}; };
static const struct aspeed_spi_data ast2600_spi_data = { static const struct aspeed_spi_data ast2600_spi_data = {
@ -1165,10 +1496,13 @@ static const struct aspeed_spi_data ast2600_spi_data = {
.timing = CE0_TIMING_COMPENSATION_REG, .timing = CE0_TIMING_COMPENSATION_REG,
.hclk_mask = 0xf0fff0ff, .hclk_mask = 0xf0fff0ff,
.hdiv_max = 2, .hdiv_max = 2,
.min_window_size = 0x200000,
.get_clk_div = aspeed_get_clk_div_ast2600,
.calibrate = aspeed_spi_ast2600_calibrate, .calibrate = aspeed_spi_ast2600_calibrate,
.segment_start = aspeed_spi_segment_ast2600_start, .segment_start = aspeed_spi_segment_ast2600_start,
.segment_end = aspeed_spi_segment_ast2600_end, .segment_end = aspeed_spi_segment_ast2600_end,
.segment_reg = aspeed_spi_segment_ast2600_reg, .segment_reg = aspeed_spi_segment_ast2600_reg,
.adjust_window = aspeed_adjust_window_ast2600,
}; };
static const struct of_device_id aspeed_spi_matches[] = { static const struct of_device_id aspeed_spi_matches[] = {