From patchwork Fri Oct 4 11:59:07 2019 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Patchwork-Submitter: =?utf-8?q?C=C3=A9dric_Le_Goater?= X-Patchwork-Id: 1171766 X-Patchwork-Delegate: tudor.ambarus@gmail.com Return-Path: X-Original-To: incoming@patchwork.ozlabs.org Delivered-To: patchwork-incoming@bilbo.ozlabs.org Authentication-Results: ozlabs.org; spf=none (mailfrom) smtp.mailfrom=lists.infradead.org (client-ip=2607:7c80:54:e::133; helo=bombadil.infradead.org; envelope-from=linux-mtd-bounces+incoming=patchwork.ozlabs.org@lists.infradead.org; receiver=) Authentication-Results: ozlabs.org; dmarc=none (p=none dis=none) header.from=kaod.org Authentication-Results: ozlabs.org; dkim=pass (2048-bit key; unprotected) header.d=lists.infradead.org header.i=@lists.infradead.org header.b="Yz0DKPC8"; dkim-atps=neutral Received: from bombadil.infradead.org (bombadil.infradead.org [IPv6:2607:7c80:54:e::133]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (4096 bits) server-digest SHA256) (No client certificate requested) by ozlabs.org (Postfix) with ESMTPS id 46l7mC1pvcz9sPl for ; Fri, 4 Oct 2019 22:02:35 +1000 (AEST) DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; d=lists.infradead.org; s=bombadil.20170209; h=Sender: Content-Transfer-Encoding:Content-Type:Cc:List-Subscribe:List-Help:List-Post: List-Archive:List-Unsubscribe:List-Id:MIME-Version:References:In-Reply-To: Message-Id:Date:Subject:To:From:Reply-To:Content-ID:Content-Description: Resent-Date:Resent-From:Resent-Sender:Resent-To:Resent-Cc:Resent-Message-ID: List-Owner; bh=FRU5Pq+99umF5ty/m9EdQAtkjPRf9G2RlST4J3D+dLc=; b=Yz0DKPC8cB0pb+ VjdcNKPe2MfOdBhUN7srNFCaksFtUZ8kLA2kljU5x25AXWD5BmJIQ14sjdyZqLmOSY3Njfr0DvNE4 e7Fe0NbsBB7uM8uRpLpUbFsbG9bc86eNrirObVyiTdqHY4Ie+HKfdoX9+Xi0+lNnTwx3HzO3DxOqy noqk2PFdfoqzMDPv90Dik4yoG3muApRybaoiQnJKem5CaAXVY7Q4BedxcqJvuFQNdnp42c2oDZt/T +cOroIrFSVmViC9T5/hBt2L50b0/VUu3wttteDBTUt4gTE75Nv9EQVI1vnnKXYDtFhOJU4MKUV612 h107rdLnLLfAXkWyNG4g==; Received: from localhost ([127.0.0.1] helo=bombadil.infradead.org) by bombadil.infradead.org with esmtp (Exim 4.92.2 #3 (Red Hat Linux)) id 1iGMI2-0008G9-Ui; Fri, 04 Oct 2019 12:02:30 +0000 Received: from 20.mo5.mail-out.ovh.net ([91.121.55.239]) by bombadil.infradead.org with esmtps (Exim 4.92.2 #3 (Red Hat Linux)) id 1iGMGV-0006mO-Hi for linux-mtd@lists.infradead.org; Fri, 04 Oct 2019 12:01:00 +0000 Received: from player792.ha.ovh.net (unknown [10.108.57.49]) by mo5.mail-out.ovh.net (Postfix) with ESMTP id F1A90251955 for ; Fri, 4 Oct 2019 14:00:51 +0200 (CEST) Received: from kaod.org (lfbn-1-2229-223.w90-76.abo.wanadoo.fr [90.76.50.223]) (Authenticated sender: clg@kaod.org) by player792.ha.ovh.net (Postfix) with ESMTPSA id 6AEC0AA65B5F; Fri, 4 Oct 2019 12:00:36 +0000 (UTC) From: =?utf-8?q?C=C3=A9dric_Le_Goater?= To: linux-mtd@lists.infradead.org, Tudor Ambarus Subject: [PATCH 04/16] mtd: spi-nor: aspeed: Add read training Date: Fri, 4 Oct 2019 13:59:07 +0200 Message-Id: <20191004115919.20788-5-clg@kaod.org> X-Mailer: git-send-email 2.21.0 In-Reply-To: <20191004115919.20788-1-clg@kaod.org> References: <20191004115919.20788-1-clg@kaod.org> MIME-Version: 1.0 X-Ovh-Tracer-Id: 3779364513022446515 X-VR-SPAMSTATE: OK X-VR-SPAMSCORE: -100 X-VR-SPAMCAUSE: gggruggvucftvghtrhhoucdtuddrgedufedrhedugdegudcutefuodetggdotefrodftvfcurfhrohhfihhlvgemucfqggfjpdevjffgvefmvefgnecuuegrihhlohhuthemucehtddtnecusecvtfgvtghiphhivghnthhsucdlqddutddtmd X-CRM114-Version: 20100106-BlameMichelson ( TRE 0.8.0 (BSD) ) MR-646709E3 X-CRM114-CacheID: sfid-20191004_050055_927768_BBCA7119 X-CRM114-Status: GOOD ( 23.78 ) X-Spam-Score: 0.0 (/) X-Spam-Report: SpamAssassin version 3.4.2 on bombadil.infradead.org summary: Content analysis details: (0.0 points) pts rule name description ---- ---------------------- -------------------------------------------------- -0.0 RCVD_IN_DNSWL_NONE RBL: Sender listed at https://www.dnswl.org/, no trust [91.121.55.239 listed in list.dnswl.org] -0.0 SPF_PASS SPF: sender matches SPF record 0.0 SPF_HELO_NONE SPF: HELO does not publish an SPF Record X-BeenThere: linux-mtd@lists.infradead.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: Linux MTD discussion mailing list List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Cc: Mark Rutland , Vignesh Raghavendra , linux-aspeed@lists.ozlabs.org, Andrew Jeffery , Richard Weinberger , Marek Vasut , Joel Stanley , Miquel Raynal , Brian Norris , David Woodhouse , linux-arm-kernel@lists.infradead.org, =?utf-8?q?C=C3=A9dric_Le_Goater?= Sender: "linux-mtd" Errors-To: linux-mtd-bounces+incoming=patchwork.ozlabs.org@lists.infradead.org The read training algorithm first reads a golden buffer at low speed and then performs reads with different clocks and delay cycles settings to find the fastest configuration for the chip. The current implementation is based on the OpenPOWER pflash tool. For the moment, read training is only activated for SPI controllers as U-Boot should have done the read training for the FMC controller using the DMA interface. We also don't limit yet the max frequency, so it's safer not to be too optimistic on the capabilities of the boot flash. It can be deactivated at boot time with the kernel parameter : aspeed_smc.optimize_read=0 Signed-off-by: Cédric Le Goater --- drivers/mtd/spi-nor/aspeed-smc.c | 204 +++++++++++++++++++++++++++++++ 1 file changed, 204 insertions(+) diff --git a/drivers/mtd/spi-nor/aspeed-smc.c b/drivers/mtd/spi-nor/aspeed-smc.c index facd8fc16ca3..155c407c2bdf 100644 --- a/drivers/mtd/spi-nor/aspeed-smc.c +++ b/drivers/mtd/spi-nor/aspeed-smc.c @@ -17,6 +17,7 @@ #include #include #include +#include #include #define DEVICE_NAME "aspeed-smc" @@ -38,12 +39,16 @@ struct aspeed_smc_info { bool hastype; /* flash type field exists in config reg */ u8 we0; /* shift for write enable bit for CE0 */ u8 ctl0; /* offset in regs of ctl for CE0 */ + u8 timing; /* offset in regs of timing */ void (*set_4b)(struct aspeed_smc_chip *chip); + int (*optimize_read)(struct aspeed_smc_chip *chip, u32 max_freq); }; static void aspeed_smc_chip_set_4b_spi_2400(struct aspeed_smc_chip *chip); static void aspeed_smc_chip_set_4b(struct aspeed_smc_chip *chip); +static int aspeed_smc_optimize_read(struct aspeed_smc_chip *chip, + u32 max_freq); static const struct aspeed_smc_info fmc_2400_info = { .maxsize = 64 * 1024 * 1024, @@ -51,6 +56,7 @@ static const struct aspeed_smc_info fmc_2400_info = { .hastype = true, .we0 = 16, .ctl0 = 0x10, + .timing = 0x94, .set_4b = aspeed_smc_chip_set_4b, }; @@ -60,7 +66,9 @@ static const struct aspeed_smc_info spi_2400_info = { .hastype = false, .we0 = 0, .ctl0 = 0x04, + .timing = 0x14, .set_4b = aspeed_smc_chip_set_4b_spi_2400, + .optimize_read = aspeed_smc_optimize_read, }; static const struct aspeed_smc_info fmc_2500_info = { @@ -69,6 +77,7 @@ static const struct aspeed_smc_info fmc_2500_info = { .hastype = true, .we0 = 16, .ctl0 = 0x10, + .timing = 0x94, .set_4b = aspeed_smc_chip_set_4b, }; @@ -78,7 +87,9 @@ static const struct aspeed_smc_info spi_2500_info = { .hastype = false, .we0 = 16, .ctl0 = 0x10, + .timing = 0x94, .set_4b = aspeed_smc_chip_set_4b, + .optimize_read = aspeed_smc_optimize_read, }; enum aspeed_smc_ctl_reg_value { @@ -200,6 +211,12 @@ struct aspeed_smc_controller { #define SEGMENT_ADDR_REG(controller, cs) \ ((controller)->regs + SEGMENT_ADDR_REG0 + (cs) * 4) +/* + * Switch to turn off read optimisation if needed + */ +static bool optimize_read = true; +module_param(optimize_read, bool, 0644); + /* * In user mode all data bytes read or written to the chip decode address * range are transferred to or from the SPI bus. The range is treated as a @@ -761,6 +778,187 @@ static int aspeed_smc_chip_setup_init(struct aspeed_smc_chip *chip, return 0; } +#define CALIBRATE_BUF_SIZE 16384 + +static bool aspeed_smc_check_reads(struct aspeed_smc_chip *chip, + const u8 *golden_buf, u8 *test_buf) +{ + int i; + + for (i = 0; i < 10; i++) { + memcpy_fromio(test_buf, chip->ahb_base, CALIBRATE_BUF_SIZE); + if (memcmp(test_buf, golden_buf, CALIBRATE_BUF_SIZE) != 0) + return false; + } + return true; +} + +static int aspeed_smc_calibrate_reads(struct aspeed_smc_chip *chip, u32 hdiv, + const u8 *golden_buf, u8 *test_buf) +{ + struct aspeed_smc_controller *controller = chip->controller; + const struct aspeed_smc_info *info = controller->info; + int i; + int good_pass = -1, pass_count = 0; + u32 shift = (hdiv - 1) << 2; + u32 mask = ~(0xfu << shift); + u32 fread_timing_val = 0; + +#define FREAD_TPASS(i) (((i) / 2) | (((i) & 1) ? 0 : 8)) + + /* Try HCLK delay 0..5, each one with/without delay and look for a + * good pair. + */ + for (i = 0; i < 12; i++) { + bool pass; + + fread_timing_val &= mask; + fread_timing_val |= FREAD_TPASS(i) << shift; + + writel(fread_timing_val, controller->regs + info->timing); + pass = aspeed_smc_check_reads(chip, golden_buf, test_buf); + dev_dbg(chip->nor.dev, + " * [%08x] %d HCLK delay, %dns DI delay : %s", + fread_timing_val, i / 2, (i & 1) ? 0 : 4, + pass ? "PASS" : "FAIL"); + if (pass) { + pass_count++; + if (pass_count == 3) { + good_pass = i - 1; + break; + } + } else { + pass_count = 0; + } + } + + /* No good setting for this frequency */ + if (good_pass < 0) + return -1; + + /* We have at least one pass of margin, let's use first pass */ + fread_timing_val &= mask; + fread_timing_val |= FREAD_TPASS(good_pass) << shift; + writel(fread_timing_val, controller->regs + info->timing); + dev_dbg(chip->nor.dev, " * -> good is pass %d [0x%08x]", + good_pass, fread_timing_val); + return 0; +} + +static bool aspeed_smc_check_calib_data(const u8 *test_buf, u32 size) +{ + const u32 *tb32 = (const u32 *)test_buf; + u32 i, cnt = 0; + + /* We check if we have enough words that are neither all 0 + * nor all 1's so the calibration can be considered valid. + * + * I use an arbitrary threshold for now of 64 + */ + size >>= 2; + for (i = 0; i < size; i++) { + if (tb32[i] != 0 && tb32[i] != 0xffffffff) + cnt++; + } + return cnt >= 64; +} + +static const u32 aspeed_smc_hclk_divs[] = { + 0xf, /* HCLK */ + 0x7, /* HCLK/2 */ + 0xe, /* HCLK/3 */ + 0x6, /* HCLK/4 */ + 0xd, /* HCLK/5 */ +}; + +#define ASPEED_SMC_HCLK_DIV(i) \ + (aspeed_smc_hclk_divs[(i) - 1] << CONTROL_CLOCK_FREQ_SEL_SHIFT) + +static u32 aspeed_smc_default_read(struct aspeed_smc_chip *chip) +{ + /* + * Keep the 4Byte address mode on the AST2400 SPI controller. + * Other controllers set the 4Byte mode in the CE Control + * Register + */ + u32 ctl_mask = chip->controller->info == &spi_2400_info ? + CONTROL_IO_ADDRESS_4B : 0; + + return (chip->ctl_val[smc_read] & ctl_mask) | + (0x00 << 28) | /* Single bit */ + (0x00 << 24) | /* CE# max */ + (0x03 << 16) | /* use normal reads */ + (0x00 << 8) | /* HCLK/16 */ + (0x00 << 6) | /* no dummy cycle */ + (0x00); /* normal mode */ +} + +static int aspeed_smc_optimize_read(struct aspeed_smc_chip *chip, + u32 max_freq) +{ + u8 *golden_buf, *test_buf; + int i, rc, best_div = -1; + u32 save_read_val = chip->ctl_val[smc_read]; + u32 ahb_freq = chip->controller->clk_frequency; + + dev_dbg(chip->nor.dev, "AHB frequency: %d MHz", ahb_freq / 1000000); + + test_buf = kmalloc(CALIBRATE_BUF_SIZE * 2, GFP_KERNEL); + golden_buf = test_buf + CALIBRATE_BUF_SIZE; + + /* We start with the dumbest setting (keep 4Byte bit) and read + * some data + */ + chip->ctl_val[smc_read] = aspeed_smc_default_read(chip); + + writel(chip->ctl_val[smc_read], chip->ctl); + + memcpy_fromio(golden_buf, chip->ahb_base, CALIBRATE_BUF_SIZE); + + /* Establish our read mode with freq field set to 0 (HCLK/16) */ + chip->ctl_val[smc_read] = save_read_val & 0xfffff0ff; + + /* Check if calibration data is suitable */ + if (!aspeed_smc_check_calib_data(golden_buf, CALIBRATE_BUF_SIZE)) { + dev_info(chip->nor.dev, + "Calibration area too uniform, using low speed"); + writel(chip->ctl_val[smc_read], chip->ctl); + kfree(test_buf); + return 0; + } + + /* Now we iterate the HCLK dividers until we find our breaking point */ + for (i = ARRAY_SIZE(aspeed_smc_hclk_divs); i > 0; i--) { + u32 tv, freq; + + /* Compare timing to max */ + freq = ahb_freq / i; + if (freq > max_freq) + continue; + + /* Set the timing */ + tv = chip->ctl_val[smc_read] | ASPEED_SMC_HCLK_DIV(i); + writel(tv, chip->ctl); + dev_dbg(chip->nor.dev, "Trying HCLK/%d...", i); + rc = aspeed_smc_calibrate_reads(chip, i, golden_buf, test_buf); + if (rc == 0) + best_div = i; + } + kfree(test_buf); + + /* Nothing found ? */ + if (best_div < 0) { + dev_warn(chip->nor.dev, "No good frequency, using dumb slow"); + } else { + dev_dbg(chip->nor.dev, "Found good read timings at HCLK/%d", + best_div); + chip->ctl_val[smc_read] |= ASPEED_SMC_HCLK_DIV(best_div); + } + + writel(chip->ctl_val[smc_read], chip->ctl); + return 0; +} + static int aspeed_smc_chip_setup_finish(struct aspeed_smc_chip *chip) { struct aspeed_smc_controller *controller = chip->controller; @@ -803,6 +1001,12 @@ static int aspeed_smc_chip_setup_finish(struct aspeed_smc_chip *chip) dev_info(controller->dev, "read control register: %08x\n", chip->ctl_val[smc_read]); + + /* + * TODO: get max freq from chip + */ + if (optimize_read && info->optimize_read) + info->optimize_read(chip, 104000000); return 0; }