From 6293b0361d9816dc5286cd766d4865a30ebdfb6f Mon Sep 17 00:00:00 2001 From: Miquel Raynal Date: Thu, 11 Oct 2018 17:45:42 +0200 Subject: [PATCH 01/28] mtd: nand: pxa3xx: add raw read support Raw read support is added by editing a few code sections: ->handle_data_pio() includes the ECC bytes that are not consumed anymore by the ECC engine. ->prepare_set_command() is changed so that the ECC bytes are requested as part of the data I/O length. ->drain_fifo() shall also avoid checking the R/B pin too often when in raw mode. ->read_page_raw()/->read_oob_raw() are written from scratch. Signed-off-by: Miquel Raynal Acked-by: Jagan Teki --- drivers/mtd/nand/raw/pxa3xx_nand.c | 98 ++++++++++++++++++++++++++++-- 1 file changed, 92 insertions(+), 6 deletions(-) diff --git a/drivers/mtd/nand/raw/pxa3xx_nand.c b/drivers/mtd/nand/raw/pxa3xx_nand.c index 4c783f1e1e4..454597355b7 100644 --- a/drivers/mtd/nand/raw/pxa3xx_nand.c +++ b/drivers/mtd/nand/raw/pxa3xx_nand.c @@ -195,6 +195,7 @@ struct pxa3xx_nand_info { int cs; int use_ecc; /* use HW ECC ? */ + int force_raw; /* prevent use_ecc to be set */ int ecc_bch; /* using BCH ECC? */ int use_spare; /* use spare ? */ int need_wait; @@ -579,7 +580,7 @@ static void disable_int(struct pxa3xx_nand_info *info, uint32_t int_mask) static void drain_fifo(struct pxa3xx_nand_info *info, void *data, int len) { - if (info->ecc_bch) { + if (info->ecc_bch && !info->force_raw) { u32 ts; /* @@ -612,12 +613,22 @@ static void drain_fifo(struct pxa3xx_nand_info *info, void *data, int len) static void handle_data_pio(struct pxa3xx_nand_info *info) { + int data_len = info->step_chunk_size; + + /* + * In raw mode, include the spare area and the ECC bytes that are not + * consumed by the controller in the data section. Do not reorganize + * here, do it in the ->read_page_raw() handler instead. + */ + if (info->force_raw) + data_len += info->step_spare_size + info->ecc_size; + switch (info->state) { case STATE_PIO_WRITING: if (info->step_chunk_size) writesl(info->mmio_base + NDDB, info->data_buff + info->data_buff_pos, - DIV_ROUND_UP(info->step_chunk_size, 4)); + DIV_ROUND_UP(data_len, 4)); if (info->step_spare_size) writesl(info->mmio_base + NDDB, @@ -628,7 +639,10 @@ static void handle_data_pio(struct pxa3xx_nand_info *info) if (info->step_chunk_size) drain_fifo(info, info->data_buff + info->data_buff_pos, - DIV_ROUND_UP(info->step_chunk_size, 4)); + DIV_ROUND_UP(data_len, 4)); + + if (info->force_raw) + break; if (info->step_spare_size) drain_fifo(info, @@ -642,7 +656,7 @@ static void handle_data_pio(struct pxa3xx_nand_info *info) } /* Update buffer pointers for multi-page read/write */ - info->data_buff_pos += info->step_chunk_size; + info->data_buff_pos += data_len; info->oob_buff_pos += info->step_spare_size; } @@ -796,7 +810,8 @@ static void prepare_start_command(struct pxa3xx_nand_info *info, int command) case NAND_CMD_READ0: case NAND_CMD_READOOB: case NAND_CMD_PAGEPROG: - info->use_ecc = 1; + if (!info->force_raw) + info->use_ecc = 1; break; case NAND_CMD_PARAM: info->use_spare = 0; @@ -866,7 +881,13 @@ static int prepare_set_command(struct pxa3xx_nand_info *info, int command, * which is either naked-read or last-read according to the * state. */ - if (mtd->writesize == info->chunk_size) { + if (info->force_raw) { + info->ndcb0 |= NDCB0_DBC | (NAND_CMD_READSTART << 8) | + NDCB0_LEN_OVRD | + NDCB0_EXT_CMD_TYPE(ext_cmd_type); + info->ndcb3 = info->step_chunk_size + + info->step_spare_size + info->ecc_size; + } else if (mtd->writesize == info->chunk_size) { info->ndcb0 |= NDCB0_DBC | (NAND_CMD_READSTART << 8); } else if (mtd->writesize > info->chunk_size) { info->ndcb0 |= NDCB0_DBC | (NAND_CMD_READSTART << 8) @@ -1238,6 +1259,69 @@ static int pxa3xx_nand_read_page_hwecc(struct mtd_info *mtd, return info->max_bitflips; } +static int pxa3xx_nand_read_page_raw(struct mtd_info *mtd, + struct nand_chip *chip, uint8_t *buf, + int oob_required, int page) +{ + struct pxa3xx_nand_host *host = chip->priv; + struct pxa3xx_nand_info *info = host->info_data; + int chunk, ecc_off_buf; + + if (!info->ecc_bch) + return -ENOTSUPP; + + /* + * Set the force_raw boolean, then re-call ->cmdfunc() that will run + * pxa3xx_nand_start(), which will actually disable the ECC engine. + */ + info->force_raw = true; + chip->cmdfunc(mtd, NAND_CMD_READ0, 0x00, page); + + ecc_off_buf = (info->nfullchunks * info->spare_size) + + info->last_spare_size; + for (chunk = 0; chunk < info->nfullchunks; chunk++) { + chip->read_buf(mtd, + buf + (chunk * info->chunk_size), + info->chunk_size); + chip->read_buf(mtd, + chip->oob_poi + + (chunk * (info->spare_size)), + info->spare_size); + chip->read_buf(mtd, + chip->oob_poi + ecc_off_buf + + (chunk * (info->ecc_size)), + info->ecc_size - 2); + } + + if (info->ntotalchunks > info->nfullchunks) { + chip->read_buf(mtd, + buf + (info->nfullchunks * info->chunk_size), + info->last_chunk_size); + chip->read_buf(mtd, + chip->oob_poi + + (info->nfullchunks * (info->spare_size)), + info->last_spare_size); + chip->read_buf(mtd, + chip->oob_poi + ecc_off_buf + + (info->nfullchunks * (info->ecc_size)), + info->ecc_size - 2); + } + + info->force_raw = false; + + return 0; +} + +static int pxa3xx_nand_read_oob_raw(struct mtd_info *mtd, + struct nand_chip *chip, int page) +{ + /* Invalidate page cache */ + chip->pagebuf = -1; + + return chip->ecc.read_page_raw(mtd, chip, chip->buffers->databuf, true, + page); +} + static uint8_t pxa3xx_nand_read_byte(struct mtd_info *mtd) { struct nand_chip *chip = mtd_to_nand(mtd); @@ -1669,6 +1753,8 @@ static int alloc_nand_resource(struct pxa3xx_nand_info *info) nand_set_controller_data(chip, host); chip->ecc.read_page = pxa3xx_nand_read_page_hwecc; + chip->ecc.read_page_raw = pxa3xx_nand_read_page_raw; + chip->ecc.read_oob_raw = pxa3xx_nand_read_oob_raw; chip->ecc.write_page = pxa3xx_nand_write_page_hwecc; chip->controller = &info->controller; chip->waitfunc = pxa3xx_nand_waitfunc; From af61ea27f51fce62188276d7b5682ac51b03a705 Mon Sep 17 00:00:00 2001 From: Miquel Raynal Date: Thu, 11 Oct 2018 17:45:43 +0200 Subject: [PATCH 02/28] mtd: nand: pxa3xx: re-read a page in raw mode on uncorrectable error This only applies on BCH path. When an empty page is read, it triggers an uncorrectable error. While this is expected, the ECC engine might produce itself bitflips in the read data under certain layouts. To overcome this situation, always re-read the entire page in raw mode and check for the whole page to be empty. Also report the right number of bitflips if there are any. Signed-off-by: Miquel Raynal Acked-by: Jagan Teki --- drivers/mtd/nand/raw/pxa3xx_nand.c | 27 +++++++++++++++++++++++---- 1 file changed, 23 insertions(+), 4 deletions(-) diff --git a/drivers/mtd/nand/raw/pxa3xx_nand.c b/drivers/mtd/nand/raw/pxa3xx_nand.c index 454597355b7..492485b1d0c 100644 --- a/drivers/mtd/nand/raw/pxa3xx_nand.c +++ b/drivers/mtd/nand/raw/pxa3xx_nand.c @@ -1237,6 +1237,7 @@ static int pxa3xx_nand_read_page_hwecc(struct mtd_info *mtd, { struct pxa3xx_nand_host *host = nand_get_controller_data(chip); struct pxa3xx_nand_info *info = host->info_data; + int bf; chip->read_buf(mtd, buf, mtd->writesize); chip->read_buf(mtd, chip->oob_poi, mtd->oobsize); @@ -1244,12 +1245,30 @@ static int pxa3xx_nand_read_page_hwecc(struct mtd_info *mtd, if (info->retcode == ERR_CORERR && info->use_ecc) { mtd->ecc_stats.corrected += info->ecc_err_cnt; - } else if (info->retcode == ERR_UNCORERR) { + } else if (info->retcode == ERR_UNCORERR && info->ecc_bch) { /* - * for blank page (all 0xff), HW will calculate its ECC as - * 0, which is different from the ECC information within - * OOB, ignore such uncorrectable errors + * Empty pages will trigger uncorrectable errors. Re-read the + * entire page in raw mode and check for bits not being "1". + * If there are more than the supported strength, then it means + * this is an actual uncorrectable error. */ + chip->ecc.read_page_raw(mtd, chip, buf, oob_required, page); + bf = nand_check_erased_ecc_chunk(buf, mtd->writesize, + chip->oob_poi, mtd->oobsize, + NULL, 0, chip->ecc.strength); + if (bf < 0) { + mtd->ecc_stats.failed++; + } else if (bf) { + mtd->ecc_stats.corrected += bf; + info->max_bitflips = max_t(unsigned int, + info->max_bitflips, bf); + info->retcode = ERR_CORERR; + } else { + info->retcode = ERR_NONE; + } + + } else if (info->retcode == ERR_UNCORERR && !info->ecc_bch) { + /* Raw read is not supported with Hamming ECC engine */ if (is_buf_blank(buf, mtd->writesize)) info->retcode = ERR_NONE; else From c907464a0ad5f1327a3873e9d0ffd617a0182a44 Mon Sep 17 00:00:00 2001 From: Miquel Raynal Date: Thu, 11 Oct 2018 17:45:44 +0200 Subject: [PATCH 03/28] mtd: rawnand: pxa3xx: fix 2kiB pages with 8b strength chips layout The initial layout for such NAND chips was the following: +----------------------------------------------------------------------------+ | 1024 (data) | 30 (ECC) | 1024 (data) | 30 (ECC) | 32 (free OOB) | 30 (ECC) | +----------------------------------------------------------------------------+ This layout has a weakness: reading empty pages trigger ECC errors (this is expected), but the hardware ECC engine tries to correct the data anyway and creates itself bitflips, hence bitflips are detected in erased pages while actually there are none in the NAND chip. Two solutions have been found at the same time. One was to enlarge the free OOB area to 64 bytes, changing the layout to be: +----------------------------------------------------------------------------+ | 1024 (data) | 30 (ECC) | 1024 (data) | 30 (ECC) | 64 (free OOB) | 30 (ECC) | +----------------------------------------------------------------------------+ ^^ The very big drawbacks of this solution are: 1/ It prevents booting from NAND. 2/ The current Linux driver (marvell_nand) does not have such problem because it already re-reads possible empty pages in raw mode before checking for bitflips. Using different layouts in U-Boot and Linux would simply not work. As this driver does support raw reads now and uses it to check for empty pages, let's forget about this broken hack and return to the initial layout with only 32 free OOB bytes. Fixes: ac56a3b30c ("mtd: nand: pxa3xx: add support for 2KB 8-bit flash") Signed-off-by: Miquel Raynal Acked-by: Jagan Teki --- drivers/mtd/nand/raw/pxa3xx_nand.c | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/drivers/mtd/nand/raw/pxa3xx_nand.c b/drivers/mtd/nand/raw/pxa3xx_nand.c index 492485b1d0c..4d2712df4c7 100644 --- a/drivers/mtd/nand/raw/pxa3xx_nand.c +++ b/drivers/mtd/nand/raw/pxa3xx_nand.c @@ -327,14 +327,14 @@ static struct nand_ecclayout ecc_layout_2KB_bch4bit = { static struct nand_ecclayout ecc_layout_2KB_bch8bit = { .eccbytes = 64, .eccpos = { - 64, 65, 66, 67, 68, 69, 70, 71, - 72, 73, 74, 75, 76, 77, 78, 79, - 80, 81, 82, 83, 84, 85, 86, 87, - 88, 89, 90, 91, 92, 93, 94, 95, - 96, 97, 98, 99, 100, 101, 102, 103, - 104, 105, 106, 107, 108, 109, 110, 111, - 112, 113, 114, 115, 116, 117, 118, 119, - 120, 121, 122, 123, 124, 125, 126, 127}, + 32, 33, 34, 35, 36, 37, 38, 39, + 40, 41, 42, 43, 44, 45, 46, 47, + 48, 49, 50, 51, 52, 53, 54, 55, + 56, 57, 58, 59, 60, 61, 62, 63, + 64, 65, 66, 67, 68, 69, 70, 71, + 72, 73, 74, 75, 76, 77, 78, 79, + 80, 81, 82, 83, 84, 85, 86, 87, + 88, 89, 90, 91, 92, 93, 94, 95}, .oobfree = { {1, 4}, {6, 26} } }; @@ -1591,7 +1591,7 @@ static int pxa_ecc_init(struct pxa3xx_nand_info *info, info->chunk_size = 1024; info->spare_size = 0; info->last_chunk_size = 1024; - info->last_spare_size = 64; + info->last_spare_size = 32; info->ecc_size = 32; ecc->mode = NAND_ECC_HW; ecc->size = info->chunk_size; From d821e5edfb8023dc01baa8e1fc0487485f313db2 Mon Sep 17 00:00:00 2001 From: Stefan Roese Date: Wed, 31 Oct 2018 12:37:20 +0100 Subject: [PATCH 04/28] cmd: ubi: Make ubi_detach() static Its only called from this file, so make it static. While at it, remove some occurances of multiple blank lines as well. Signed-off-by: Stefan Roese Cc: Boris Brezillon Cc: Miquel Raynal Reviewed-by: Miquel Raynal Reviewed-by: Boris Brezillon Reviewed-by: Jagan Teki --- cmd/ubi.c | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/cmd/ubi.c b/cmd/ubi.c index 2b74a981446..a12ac703ebe 100644 --- a/cmd/ubi.c +++ b/cmd/ubi.c @@ -101,7 +101,6 @@ static int ubi_check(char *name) return 1; } - static int verify_mkvol_req(const struct ubi_device *ubi, const struct ubi_mkvol_req *req) { @@ -415,7 +414,7 @@ static int ubi_dev_scan(struct mtd_info *info, const char *vid_header_offset) return 0; } -int ubi_detach(void) +static int ubi_detach(void) { #ifdef CONFIG_CMD_UBIFS /* @@ -473,7 +472,6 @@ static int do_ubi(cmd_tbl_t *cmdtp, int flag, int argc, char * const argv[]) if (argc < 2) return CMD_RET_USAGE; - if (strcmp(argv[1], "detach") == 0) { if (argc < 2) return CMD_RET_USAGE; @@ -481,7 +479,6 @@ static int do_ubi(cmd_tbl_t *cmdtp, int flag, int argc, char * const argv[]) return ubi_detach(); } - if (strcmp(argv[1], "part") == 0) { const char *vid_header_offset = NULL; From d13801ef1d14fe46f97630b56e01200b337dad6c Mon Sep 17 00:00:00 2001 From: Neil Armstrong Date: Thu, 22 Nov 2018 11:01:03 +0100 Subject: [PATCH 05/28] regmap: add regmap_read_poll_timeout() helper Add the regmap_read_poll_timeout() macro based on the Linux implementation to simplify register polling with configurable timeout and sleep. Tested-by: Jerome Brunet Acked-by: Jagan Teki Signed-off-by: Neil Armstrong --- include/regmap.h | 38 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/include/regmap.h b/include/regmap.h index b2b733fda68..a3afb72df51 100644 --- a/include/regmap.h +++ b/include/regmap.h @@ -239,6 +239,44 @@ int regmap_raw_read_range(struct regmap *map, uint range_num, uint offset, #define regmap_get(map, type, member, valp) \ regmap_range_get(map, 0, type, member, valp) +/** + * regmap_read_poll_timeout - Poll until a condition is met or a timeout occurs + * + * @map: Regmap to read from + * @addr: Offset to poll + * @val: Unsigned integer variable to read the value into + * @cond: Break condition (usually involving @val) + * @sleep_us: Maximum time to sleep between reads in us (0 tight-loops). + * @timeout_ms: Timeout in ms, 0 means never timeout + * + * Returns 0 on success and -ETIMEDOUT upon a timeout or the regmap_read + * error return value in case of a error read. In the two former cases, + * the last read value at @addr is stored in @val. Must not be called + * from atomic context if sleep_us or timeout_us are used. + * + * This is modelled after the regmap_read_poll_timeout macros in linux but + * with millisecond timeout. + */ +#define regmap_read_poll_timeout(map, addr, val, cond, sleep_us, timeout_ms) \ +({ \ + unsigned long __start = get_timer(0); \ + int __ret; \ + for (;;) { \ + __ret = regmap_read((map), (addr), &(val)); \ + if (__ret) \ + break; \ + if (cond) \ + break; \ + if ((timeout_ms) && get_timer(__start) > (timeout_ms)) { \ + __ret = regmap_read((map), (addr), &(val)); \ + break; \ + } \ + if ((sleep_us)) \ + udelay((sleep_us)); \ + } \ + __ret ?: ((cond) ? 0 : -ETIMEDOUT); \ +}) + /** * regmap_update_bits() - Perform a read/modify/write using a mask * From ebe3497c9c96def46e48b57300933abe0c4c850c Mon Sep 17 00:00:00 2001 From: Neil Armstrong Date: Thu, 22 Nov 2018 11:01:04 +0100 Subject: [PATCH 06/28] test: regmap: add regmap_read_poll_timeout test Add test to regmap_read_poll_timeout() helper to check the timeout works properly but cannot test proper condition matching since read/write calls are not executed in sandbox. Tested-by: Jerome Brunet Acked-by: Jagan Teki Signed-off-by: Neil Armstrong --- test/dm/regmap.c | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/test/dm/regmap.c b/test/dm/regmap.c index a8d7e6829ec..9a70c159ddb 100644 --- a/test/dm/regmap.c +++ b/test/dm/regmap.c @@ -144,3 +144,29 @@ static int dm_test_regmap_getset(struct unit_test_state *uts) } DM_TEST(dm_test_regmap_getset, DM_TESTF_SCAN_PDATA | DM_TESTF_SCAN_FDT); + +/* Read polling test */ +static int dm_test_regmap_poll(struct unit_test_state *uts) +{ + struct udevice *dev; + struct regmap *map; + uint reg; + unsigned long start; + + ut_assertok(uclass_get_device(UCLASS_SYSCON, 0, &dev)); + map = syscon_get_regmap(dev); + ut_assertok_ptr(map); + + start = get_timer(0); + + ut_asserteq(-ETIMEDOUT, + regmap_read_poll_timeout(map, 0, reg, + (reg == 0xcacafafa), + 1, 5 * CONFIG_SYS_HZ)); + + ut_assert(get_timer(start) > (5 * CONFIG_SYS_HZ)); + + return 0; +} + +DM_TEST(dm_test_regmap_poll, DM_TESTF_SCAN_PDATA | DM_TESTF_SCAN_FDT); From 9d26506a9ca9b13f114acae470bd9c8352484397 Mon Sep 17 00:00:00 2001 From: Neil Armstrong Date: Thu, 22 Nov 2018 11:01:05 +0100 Subject: [PATCH 07/28] spi: Add Amlogic Meson SPI Flash Controller driver The Amlogic Meson SoCs embeds a Flash oriented SPI Controller name SPIFC. This driver, ported from the Linux meson-spi-spifc driver, add support for this controller on the Amlogic Meson GX SoCs in U-Boot. Tested-by: Jerome Brunet Reviewed-by: Jagan Teki Signed-off-by: Neil Armstrong --- drivers/spi/Kconfig | 8 + drivers/spi/Makefile | 1 + drivers/spi/meson_spifc.c | 320 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 329 insertions(+) create mode 100644 drivers/spi/meson_spifc.c diff --git a/drivers/spi/Kconfig b/drivers/spi/Kconfig index 516188ea88e..60857884814 100644 --- a/drivers/spi/Kconfig +++ b/drivers/spi/Kconfig @@ -116,6 +116,14 @@ config ICH_SPI access the SPI NOR flash on platforms embedding this Intel ICH IP core. +config MESON_SPIFC + bool "Amlogic Meson SPI Flash Controller driver" + depends on ARCH_MESON + help + Enable the Amlogic Meson SPI Flash Controller SPIFC) driver. + This driver can be used to access the SPI NOR flash chips on + Amlogic Meson SoCs. + config MT7621_SPI bool "MediaTek MT7621 SPI driver" depends on ARCH_MT7620 diff --git a/drivers/spi/Makefile b/drivers/spi/Makefile index 7242ea7e404..67b42daf4ed 100644 --- a/drivers/spi/Makefile +++ b/drivers/spi/Makefile @@ -31,6 +31,7 @@ obj-$(CONFIG_FSL_QSPI) += fsl_qspi.o obj-$(CONFIG_ICH_SPI) += ich.o obj-$(CONFIG_KIRKWOOD_SPI) += kirkwood_spi.o obj-$(CONFIG_LPC32XX_SSP) += lpc32xx_ssp.o +obj-$(CONFIG_MESON_SPIFC) += meson_spifc.o obj-$(CONFIG_MPC8XX_SPI) += mpc8xx_spi.o obj-$(CONFIG_MPC8XXX_SPI) += mpc8xxx_spi.o obj-$(CONFIG_MT7621_SPI) += mt7621_spi.o diff --git a/drivers/spi/meson_spifc.c b/drivers/spi/meson_spifc.c new file mode 100644 index 00000000000..3d551694cb0 --- /dev/null +++ b/drivers/spi/meson_spifc.c @@ -0,0 +1,320 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (C) 2014 Beniamino Galvani + * Copyright (C) 2018 BayLibre, SAS + * Author: Neil Armstrong + * + * Amlogic Meson SPI Flash Controller driver + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +/* register map */ +#define REG_CMD 0x00 +#define REG_ADDR 0x04 +#define REG_CTRL 0x08 +#define REG_CTRL1 0x0c +#define REG_STATUS 0x10 +#define REG_CTRL2 0x14 +#define REG_CLOCK 0x18 +#define REG_USER 0x1c +#define REG_USER1 0x20 +#define REG_USER2 0x24 +#define REG_USER3 0x28 +#define REG_USER4 0x2c +#define REG_SLAVE 0x30 +#define REG_SLAVE1 0x34 +#define REG_SLAVE2 0x38 +#define REG_SLAVE3 0x3c +#define REG_C0 0x40 +#define REG_B8 0x60 +#define REG_MAX 0x7c + +/* register fields */ +#define CMD_USER BIT(18) +#define CTRL_ENABLE_AHB BIT(17) +#define CLOCK_SOURCE BIT(31) +#define CLOCK_DIV_SHIFT 12 +#define CLOCK_DIV_MASK (0x3f << CLOCK_DIV_SHIFT) +#define CLOCK_CNT_HIGH_SHIFT 6 +#define CLOCK_CNT_HIGH_MASK (0x3f << CLOCK_CNT_HIGH_SHIFT) +#define CLOCK_CNT_LOW_SHIFT 0 +#define CLOCK_CNT_LOW_MASK (0x3f << CLOCK_CNT_LOW_SHIFT) +#define USER_DIN_EN_MS BIT(0) +#define USER_CMP_MODE BIT(2) +#define USER_CLK_NOT_INV BIT(7) +#define USER_UC_DOUT_SEL BIT(27) +#define USER_UC_DIN_SEL BIT(28) +#define USER_UC_MASK ((BIT(5) - 1) << 27) +#define USER1_BN_UC_DOUT_SHIFT 17 +#define USER1_BN_UC_DOUT_MASK (0xff << 16) +#define USER1_BN_UC_DIN_SHIFT 8 +#define USER1_BN_UC_DIN_MASK (0xff << 8) +#define USER4_CS_POL_HIGH BIT(23) +#define USER4_IDLE_CLK_HIGH BIT(29) +#define USER4_CS_ACT BIT(30) +#define SLAVE_TRST_DONE BIT(4) +#define SLAVE_OP_MODE BIT(30) +#define SLAVE_SW_RST BIT(31) + +#define SPIFC_BUFFER_SIZE 64 + +struct meson_spifc_priv { + struct regmap *regmap; + struct clk clk; +}; + +/** + * meson_spifc_drain_buffer() - copy data from device buffer to memory + * @spifc: the Meson SPI device + * @buf: the destination buffer + * @len: number of bytes to copy + */ +static void meson_spifc_drain_buffer(struct meson_spifc_priv *spifc, + u8 *buf, int len) +{ + u32 data; + int i = 0; + + while (i < len) { + regmap_read(spifc->regmap, REG_C0 + i, &data); + + if (len - i >= 4) { + *((u32 *)buf) = data; + buf += 4; + } else { + memcpy(buf, &data, len - i); + break; + } + i += 4; + } +} + +/** + * meson_spifc_fill_buffer() - copy data from memory to device buffer + * @spifc: the Meson SPI device + * @buf: the source buffer + * @len: number of bytes to copy + */ +static void meson_spifc_fill_buffer(struct meson_spifc_priv *spifc, + const u8 *buf, int len) +{ + u32 data = 0; + int i = 0; + + while (i < len) { + if (len - i >= 4) + data = *(u32 *)buf; + else + memcpy(&data, buf, len - i); + + regmap_write(spifc->regmap, REG_C0 + i, data); + + buf += 4; + i += 4; + } +} + +/** + * meson_spifc_txrx() - transfer a chunk of data + * @spifc: the Meson SPI device + * @dout: data buffer for TX + * @din: data buffer for RX + * @offset: offset of the data to transfer + * @len: length of the data to transfer + * @last_xfer: whether this is the last transfer of the message + * @last_chunk: whether this is the last chunk of the transfer + * Return: 0 on success, a negative value on error + */ +static int meson_spifc_txrx(struct meson_spifc_priv *spifc, + const u8 *dout, u8 *din, int offset, + int len, bool last_xfer, bool last_chunk) +{ + bool keep_cs = true; + u32 data; + int ret; + + if (dout) + meson_spifc_fill_buffer(spifc, dout + offset, len); + + /* enable DOUT stage */ + regmap_update_bits(spifc->regmap, REG_USER, USER_UC_MASK, + USER_UC_DOUT_SEL); + regmap_write(spifc->regmap, REG_USER1, + (8 * len - 1) << USER1_BN_UC_DOUT_SHIFT); + + /* enable data input during DOUT */ + regmap_update_bits(spifc->regmap, REG_USER, USER_DIN_EN_MS, + USER_DIN_EN_MS); + + if (last_chunk && last_xfer) + keep_cs = false; + + regmap_update_bits(spifc->regmap, REG_USER4, USER4_CS_ACT, + keep_cs ? USER4_CS_ACT : 0); + + /* clear transition done bit */ + regmap_update_bits(spifc->regmap, REG_SLAVE, SLAVE_TRST_DONE, 0); + /* start transfer */ + regmap_update_bits(spifc->regmap, REG_CMD, CMD_USER, CMD_USER); + + /* wait for the current operation to terminate */ + ret = regmap_read_poll_timeout(spifc->regmap, REG_SLAVE, data, + (data & SLAVE_TRST_DONE), + 0, 5 * CONFIG_SYS_HZ); + + if (!ret && din) + meson_spifc_drain_buffer(spifc, din + offset, len); + + return ret; +} + +/** + * meson_spifc_xfer() - perform a single transfer + * @dev: the SPI controller device + * @bitlen: length of the transfer + * @dout: data buffer for TX + * @din: data buffer for RX + * @flags: transfer flags + * Return: 0 on success, a negative value on error + */ +static int meson_spifc_xfer(struct udevice *slave, unsigned int bitlen, + const void *dout, void *din, unsigned long flags) +{ + struct meson_spifc_priv *spifc = dev_get_priv(slave->parent); + int blen = bitlen / 8; + int len, done = 0, ret = 0; + + if (bitlen % 8) + return -EINVAL; + + debug("xfer len %d (%d) dout %p din %p\n", bitlen, blen, dout, din); + + regmap_update_bits(spifc->regmap, REG_CTRL, CTRL_ENABLE_AHB, 0); + + while (done < blen && !ret) { + len = min_t(int, blen - done, SPIFC_BUFFER_SIZE); + ret = meson_spifc_txrx(spifc, dout, din, done, len, + flags & SPI_XFER_END, + done + len >= blen); + done += len; + } + + regmap_update_bits(spifc->regmap, REG_CTRL, CTRL_ENABLE_AHB, + CTRL_ENABLE_AHB); + + return ret; +} + +/** + * meson_spifc_set_speed() - program the clock divider + * @dev: the SPI controller device + * @speed: desired speed in Hz + */ +static int meson_spifc_set_speed(struct udevice *dev, uint speed) +{ + struct meson_spifc_priv *spifc = dev_get_priv(dev); + unsigned long parent, value; + int n; + + parent = clk_get_rate(&spifc->clk); + n = max_t(int, parent / speed - 1, 1); + + debug("parent %lu, speed %u, n %d\n", parent, speed, n); + + value = (n << CLOCK_DIV_SHIFT) & CLOCK_DIV_MASK; + value |= (n << CLOCK_CNT_LOW_SHIFT) & CLOCK_CNT_LOW_MASK; + value |= (((n + 1) / 2 - 1) << CLOCK_CNT_HIGH_SHIFT) & + CLOCK_CNT_HIGH_MASK; + + regmap_write(spifc->regmap, REG_CLOCK, value); + + return 0; +} + +/** + * meson_spifc_set_mode() - setups the SPI bus mode + * @dev: the SPI controller device + * @mode: desired mode bitfield + * Return: 0 on success, -ENODEV on error + */ +static int meson_spifc_set_mode(struct udevice *dev, uint mode) +{ + struct meson_spifc_priv *spifc = dev_get_priv(dev); + + if (mode & (SPI_CPHA | SPI_RX_QUAD | SPI_RX_DUAL | + SPI_TX_QUAD | SPI_TX_DUAL)) + return -ENODEV; + + regmap_update_bits(spifc->regmap, REG_USER, USER_CLK_NOT_INV, + mode & SPI_CPOL ? USER_CLK_NOT_INV : 0); + + regmap_update_bits(spifc->regmap, REG_USER4, USER4_CS_POL_HIGH, + mode & SPI_CS_HIGH ? USER4_CS_POL_HIGH : 0); + + return 0; +} + +/** + * meson_spifc_hw_init() - reset and initialize the SPI controller + * @spifc: the Meson SPI device + */ +static void meson_spifc_hw_init(struct meson_spifc_priv *spifc) +{ + /* reset device */ + regmap_update_bits(spifc->regmap, REG_SLAVE, SLAVE_SW_RST, + SLAVE_SW_RST); + /* disable compatible mode */ + regmap_update_bits(spifc->regmap, REG_USER, USER_CMP_MODE, 0); + /* set master mode */ + regmap_update_bits(spifc->regmap, REG_SLAVE, SLAVE_OP_MODE, 0); +} + +static const struct dm_spi_ops meson_spifc_ops = { + .xfer = meson_spifc_xfer, + .set_speed = meson_spifc_set_speed, + .set_mode = meson_spifc_set_mode, +}; + +static int meson_spifc_probe(struct udevice *dev) +{ + struct meson_spifc_priv *priv = dev_get_priv(dev); + int ret; + + ret = regmap_init_mem(dev_ofnode(dev), &priv->regmap); + if (ret) + return ret; + + ret = clk_get_by_index(dev, 0, &priv->clk); + if (ret) + return ret; + + ret = clk_enable(&priv->clk); + if (ret) + return ret; + + meson_spifc_hw_init(priv); + + return 0; +} + +static const struct udevice_id meson_spifc_ids[] = { + { .compatible = "amlogic,meson-gxbb-spifc", }, + { } +}; + +U_BOOT_DRIVER(meson_spifc) = { + .name = "meson_spifc", + .id = UCLASS_SPI, + .of_match = meson_spifc_ids, + .ops = &meson_spifc_ops, + .probe = meson_spifc_probe, + .priv_auto_alloc_size = sizeof(struct meson_spifc_priv), +}; From 052cafd2a56e90270aea7f0335a232ca2dea3c99 Mon Sep 17 00:00:00 2001 From: Guochun Mao Date: Fri, 12 Oct 2018 15:01:06 +0800 Subject: [PATCH 08/28] spi: mtk_qspi: add qspi driver for MT7629 SoC This patch adds MT7629 qspi driver for accessing SPI NOR flash. Signed-off-by: Guochun Mao Reviewed-by: Simon Glass Reviewed-by: Jagan Teki --- drivers/spi/Kconfig | 7 + drivers/spi/Makefile | 1 + drivers/spi/mtk_qspi.c | 359 +++++++++++++++++++++++++++++++++++++++++ 3 files changed, 367 insertions(+) create mode 100644 drivers/spi/mtk_qspi.c diff --git a/drivers/spi/Kconfig b/drivers/spi/Kconfig index 60857884814..4a8a38b863c 100644 --- a/drivers/spi/Kconfig +++ b/drivers/spi/Kconfig @@ -132,6 +132,13 @@ config MT7621_SPI the SPI NOR flash on platforms embedding this Ralink / MediaTek SPI core, like MT7621/7628/7688. +config MTK_QSPI + bool "Mediatek QSPI driver" + help + Enable the Mediatek QSPI driver. This driver can be + used to access the SPI NOR flash on platforms embedding this + Mediatek QSPI IP core. + config MVEBU_A3700_SPI bool "Marvell Armada 3700 SPI driver" select CLK_ARMADA_3720 diff --git a/drivers/spi/Makefile b/drivers/spi/Makefile index 67b42daf4ed..392a9257957 100644 --- a/drivers/spi/Makefile +++ b/drivers/spi/Makefile @@ -34,6 +34,7 @@ obj-$(CONFIG_LPC32XX_SSP) += lpc32xx_ssp.o obj-$(CONFIG_MESON_SPIFC) += meson_spifc.o obj-$(CONFIG_MPC8XX_SPI) += mpc8xx_spi.o obj-$(CONFIG_MPC8XXX_SPI) += mpc8xxx_spi.o +obj-$(CONFIG_MTK_QSPI) += mtk_qspi.o obj-$(CONFIG_MT7621_SPI) += mt7621_spi.o obj-$(CONFIG_MVEBU_A3700_SPI) += mvebu_a3700_spi.o obj-$(CONFIG_MXC_SPI) += mxc_spi.o diff --git a/drivers/spi/mtk_qspi.c b/drivers/spi/mtk_qspi.c new file mode 100644 index 00000000000..b510733e92c --- /dev/null +++ b/drivers/spi/mtk_qspi.c @@ -0,0 +1,359 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (c) 2018 MediaTek, Inc. + * Author : Guochun.Mao@mediatek.com + */ + +#include +#include +#include +#include +#include +#include +#include + +/* Register Offset */ +struct mtk_qspi_regs { + u32 cmd; + u32 cnt; + u32 rdsr; + u32 rdata; + u32 radr[3]; + u32 wdata; + u32 prgdata[6]; + u32 shreg[10]; + u32 cfg[2]; + u32 shreg10; + u32 mode_mon; + u32 status[4]; + u32 flash_time; + u32 flash_cfg; + u32 reserved_0[3]; + u32 sf_time; + u32 pp_dw_data; + u32 reserved_1; + u32 delsel_0[2]; + u32 intrstus; + u32 intren; + u32 reserved_2; + u32 cfg3; + u32 reserved_3; + u32 chksum; + u32 aaicmd; + u32 wrprot; + u32 radr3; + u32 dual; + u32 delsel_1[3]; +}; + +struct mtk_qspi_platdata { + fdt_addr_t reg_base; + fdt_addr_t mem_base; +}; + +struct mtk_qspi_priv { + struct mtk_qspi_regs *regs; + unsigned long *mem_base; + u8 op; + u8 tx[3]; /* only record max 3 bytes paras, when it's address. */ + u32 txlen; /* dout buffer length - op code length */ + u8 *rx; + u32 rxlen; +}; + +#define MTK_QSPI_CMD_POLLINGREG_US 500000 +#define MTK_QSPI_WRBUF_SIZE 256 +#define MTK_QSPI_COMMAND_ENABLE 0x30 + +/* NOR flash controller commands */ +#define MTK_QSPI_RD_TRIGGER BIT(0) +#define MTK_QSPI_READSTATUS BIT(1) +#define MTK_QSPI_PRG_CMD BIT(2) +#define MTK_QSPI_WR_TRIGGER BIT(4) +#define MTK_QSPI_WRITESTATUS BIT(5) +#define MTK_QSPI_AUTOINC BIT(7) + +#define MTK_QSPI_MAX_RX_TX_SHIFT 0x6 +#define MTK_QSPI_MAX_SHIFT 0x8 + +#define MTK_QSPI_WR_BUF_ENABLE 0x1 +#define MTK_QSPI_WR_BUF_DISABLE 0x0 + +static int mtk_qspi_execute_cmd(struct mtk_qspi_priv *priv, u8 cmd) +{ + u8 tmp; + u8 val = cmd & ~MTK_QSPI_AUTOINC; + + writeb(cmd, &priv->regs->cmd); + + return readb_poll_timeout(&priv->regs->cmd, tmp, !(val & tmp), + MTK_QSPI_CMD_POLLINGREG_US); +} + +static int mtk_qspi_tx_rx(struct mtk_qspi_priv *priv) +{ + int len = 1 + priv->txlen + priv->rxlen; + int i, ret, idx; + + if (len > MTK_QSPI_MAX_SHIFT) + return -ERR_INVAL; + + writeb(len * 8, &priv->regs->cnt); + + /* start at PRGDATA5, go down to PRGDATA0 */ + idx = MTK_QSPI_MAX_RX_TX_SHIFT - 1; + + /* opcode */ + writeb(priv->op, &priv->regs->prgdata[idx]); + idx--; + + /* program TX data */ + for (i = 0; i < priv->txlen; i++, idx--) + writeb(priv->tx[i], &priv->regs->prgdata[idx]); + + /* clear out rest of TX registers */ + while (idx >= 0) { + writeb(0, &priv->regs->prgdata[idx]); + idx--; + } + + ret = mtk_qspi_execute_cmd(priv, MTK_QSPI_PRG_CMD); + if (ret) + return ret; + + /* restart at first RX byte */ + idx = priv->rxlen - 1; + + /* read out RX data */ + for (i = 0; i < priv->rxlen; i++, idx--) + priv->rx[i] = readb(&priv->regs->shreg[idx]); + + return 0; +} + +static int mtk_qspi_read(struct mtk_qspi_priv *priv, + u32 addr, u8 *buf, u32 len) +{ + memcpy(buf, (u8 *)priv->mem_base + addr, len); + return 0; +} + +static void mtk_qspi_set_addr(struct mtk_qspi_priv *priv, u32 addr) +{ + int i; + + for (i = 0; i < 3; i++) { + writeb(addr & 0xff, &priv->regs->radr[i]); + addr >>= 8; + } +} + +static int mtk_qspi_write_single_byte(struct mtk_qspi_priv *priv, + u32 addr, u32 length, const u8 *data) +{ + int i, ret; + + mtk_qspi_set_addr(priv, addr); + + for (i = 0; i < length; i++) { + writeb(*data++, &priv->regs->wdata); + ret = mtk_qspi_execute_cmd(priv, MTK_QSPI_WR_TRIGGER); + if (ret < 0) + return ret; + } + return 0; +} + +static int mtk_qspi_write_buffer(struct mtk_qspi_priv *priv, u32 addr, + const u8 *buf) +{ + int i, data; + + mtk_qspi_set_addr(priv, addr); + + for (i = 0; i < MTK_QSPI_WRBUF_SIZE; i += 4) { + data = buf[i + 3] << 24 | buf[i + 2] << 16 | + buf[i + 1] << 8 | buf[i]; + writel(data, &priv->regs->pp_dw_data); + } + + return mtk_qspi_execute_cmd(priv, MTK_QSPI_WR_TRIGGER); +} + +static int mtk_qspi_write(struct mtk_qspi_priv *priv, + u32 addr, const u8 *buf, u32 len) +{ + int ret; + + /* setting pre-fetch buffer for page program */ + writel(MTK_QSPI_WR_BUF_ENABLE, &priv->regs->cfg[1]); + while (len >= MTK_QSPI_WRBUF_SIZE) { + ret = mtk_qspi_write_buffer(priv, addr, buf); + if (ret < 0) + return ret; + + len -= MTK_QSPI_WRBUF_SIZE; + addr += MTK_QSPI_WRBUF_SIZE; + buf += MTK_QSPI_WRBUF_SIZE; + } + /* disable pre-fetch buffer for page program */ + writel(MTK_QSPI_WR_BUF_DISABLE, &priv->regs->cfg[1]); + + if (len) + return mtk_qspi_write_single_byte(priv, addr, len, buf); + + return 0; +} + +static int mtk_qspi_claim_bus(struct udevice *dev) +{ + /* nothing to do */ + return 0; +} + +static int mtk_qspi_release_bus(struct udevice *dev) +{ + /* nothing to do */ + return 0; +} + +static int mtk_qspi_transfer(struct mtk_qspi_priv *priv, unsigned int bitlen, + const void *dout, void *din, unsigned long flags) +{ + u32 bytes = DIV_ROUND_UP(bitlen, 8); + u32 addr; + + if (!bytes) + return -ERR_INVAL; + + if (dout) { + if (flags & SPI_XFER_BEGIN) { + /* parse op code and potential paras first */ + priv->op = *(u8 *)dout; + if (bytes > 1) + memcpy(priv->tx, (u8 *)dout + 1, + bytes <= 4 ? bytes - 1 : 3); + priv->txlen = bytes - 1; + } + + if (flags == SPI_XFER_ONCE) { + /* operations without receiving or sending data. + * for example: erase, write flash register or write + * enable... + */ + priv->rx = NULL; + priv->rxlen = 0; + return mtk_qspi_tx_rx(priv); + } + + if (flags & SPI_XFER_END) { + /* here, dout should be data to be written. + * and priv->tx should be filled 3Bytes address. + */ + addr = priv->tx[0] << 16 | priv->tx[1] << 8 | + priv->tx[2]; + return mtk_qspi_write(priv, addr, (u8 *)dout, bytes); + } + } + + if (din) { + if (priv->txlen >= 3) { + /* if run to here, priv->tx[] should be the address + * where read data from, + * and, din is the buf to receive data. + */ + addr = priv->tx[0] << 16 | priv->tx[1] << 8 | + priv->tx[2]; + return mtk_qspi_read(priv, addr, (u8 *)din, bytes); + } + + /* should be reading flash's register */ + priv->rx = (u8 *)din; + priv->rxlen = bytes; + return mtk_qspi_tx_rx(priv); + } + + return 0; +} + +static int mtk_qspi_xfer(struct udevice *dev, unsigned int bitlen, + const void *dout, void *din, unsigned long flags) +{ + struct udevice *bus = dev->parent; + struct mtk_qspi_priv *priv = dev_get_priv(bus); + + return mtk_qspi_transfer(priv, bitlen, dout, din, flags); +} + +static int mtk_qspi_set_speed(struct udevice *bus, uint speed) +{ + /* nothing to do */ + return 0; +} + +static int mtk_qspi_set_mode(struct udevice *bus, uint mode) +{ + /* nothing to do */ + return 0; +} + +static int mtk_qspi_ofdata_to_platdata(struct udevice *bus) +{ + struct resource res_reg, res_mem; + struct mtk_qspi_platdata *plat = bus->platdata; + int ret; + + ret = dev_read_resource_byname(bus, "reg_base", &res_reg); + if (ret) { + debug("can't get reg_base resource(ret = %d)\n", ret); + return -ENOMEM; + } + + ret = dev_read_resource_byname(bus, "mem_base", &res_mem); + if (ret) { + debug("can't get map_base resource(ret = %d)\n", ret); + return -ENOMEM; + } + + plat->mem_base = res_mem.start; + plat->reg_base = res_reg.start; + + return 0; +} + +static int mtk_qspi_probe(struct udevice *bus) +{ + struct mtk_qspi_platdata *plat = dev_get_platdata(bus); + struct mtk_qspi_priv *priv = dev_get_priv(bus); + + priv->regs = (struct mtk_qspi_regs *)plat->reg_base; + priv->mem_base = (unsigned long *)plat->mem_base; + + writel(MTK_QSPI_COMMAND_ENABLE, &priv->regs->wrprot); + + return 0; +} + +static const struct dm_spi_ops mtk_qspi_ops = { + .claim_bus = mtk_qspi_claim_bus, + .release_bus = mtk_qspi_release_bus, + .xfer = mtk_qspi_xfer, + .set_speed = mtk_qspi_set_speed, + .set_mode = mtk_qspi_set_mode, +}; + +static const struct udevice_id mtk_qspi_ids[] = { + { .compatible = "mediatek,mt7629-qspi" }, + { } +}; + +U_BOOT_DRIVER(mtk_qspi) = { + .name = "mtk_qspi", + .id = UCLASS_SPI, + .of_match = mtk_qspi_ids, + .ops = &mtk_qspi_ops, + .ofdata_to_platdata = mtk_qspi_ofdata_to_platdata, + .platdata_auto_alloc_size = sizeof(struct mtk_qspi_platdata), + .priv_auto_alloc_size = sizeof(struct mtk_qspi_priv), + .probe = mtk_qspi_probe, +}; From 3deb1f731d9b122c86c246a7ec53bcd1d67af66f Mon Sep 17 00:00:00 2001 From: Jagan Teki Date: Wed, 14 Nov 2018 15:28:05 +0530 Subject: [PATCH 09/28] spi: pl022: Simplify platdata code pl022 spi driver support both OF_CONTROL and PLATDATA, this patch is trying to simplify the code that differentiating platdata vs of_control. - Move OF_CONTROL code at one place - Handle clock setup code directly in pl022_spi_ofdata_to_platdata Acked-by: Quentin Schulz Signed-off-by: Jagan Teki --- drivers/spi/pl022_spi.c | 48 ++++++++++++---------------- include/dm/platform_data/pl022_spi.h | 7 ---- 2 files changed, 20 insertions(+), 35 deletions(-) diff --git a/drivers/spi/pl022_spi.c b/drivers/spi/pl022_spi.c index 86b71d2e21a..05f4f6f4819 100644 --- a/drivers/spi/pl022_spi.c +++ b/drivers/spi/pl022_spi.c @@ -72,11 +72,7 @@ struct pl022_spi_slave { void *base; -#if !CONFIG_IS_ENABLED(OF_PLATDATA) - struct clk clk; -#else unsigned int freq; -#endif }; /* @@ -96,30 +92,13 @@ static int pl022_is_supported(struct pl022_spi_slave *ps) return 0; } -#if !CONFIG_IS_ENABLED(OF_PLATDATA) -static int pl022_spi_ofdata_to_platdata(struct udevice *bus) -{ - struct pl022_spi_pdata *plat = bus->platdata; - const void *fdt = gd->fdt_blob; - int node = dev_of_offset(bus); - - plat->addr = fdtdec_get_addr_size(fdt, node, "reg", &plat->size); - - return clk_get_by_index(bus, 0, &plat->clk); -} -#endif - static int pl022_spi_probe(struct udevice *bus) { struct pl022_spi_pdata *plat = dev_get_platdata(bus); struct pl022_spi_slave *ps = dev_get_priv(bus); ps->base = ioremap(plat->addr, plat->size); -#if !CONFIG_IS_ENABLED(OF_PLATDATA) - ps->clk = plat->clk; -#else ps->freq = plat->freq; -#endif /* Check the PL022 version */ if (!pl022_is_supported(ps)) @@ -240,11 +219,7 @@ static int pl022_spi_set_speed(struct udevice *bus, uint speed) u16 scr = SSP_SCR_MIN, cr0 = 0, cpsr = SSP_CPSR_MIN, best_scr = scr, best_cpsr = cpsr; u32 min, max, best_freq = 0, tmp; -#if !CONFIG_IS_ENABLED(OF_PLATDATA) - u32 rate = clk_get_rate(&ps->clk); -#else u32 rate = ps->freq; -#endif bool found = false; max = spi_rate(rate, SSP_CPSR_MIN, SSP_SCR_MIN); @@ -316,6 +291,25 @@ static const struct dm_spi_ops pl022_spi_ops = { }; #if !CONFIG_IS_ENABLED(OF_PLATDATA) +static int pl022_spi_ofdata_to_platdata(struct udevice *bus) +{ + struct pl022_spi_pdata *plat = bus->platdata; + const void *fdt = gd->fdt_blob; + int node = dev_of_offset(bus); + struct clk clkdev; + int ret; + + plat->addr = fdtdec_get_addr_size(fdt, node, "reg", &plat->size); + + ret = clk_get_by_index(bus, 0, &clkdev); + if (ret) + return ret; + + plat->freq = clk_get_rate(&clkdev); + + return 0; +} + static const struct udevice_id pl022_spi_ids[] = { { .compatible = "arm,pl022-spi" }, { } @@ -327,11 +321,9 @@ U_BOOT_DRIVER(pl022_spi) = { .id = UCLASS_SPI, #if !CONFIG_IS_ENABLED(OF_PLATDATA) .of_match = pl022_spi_ids, -#endif - .ops = &pl022_spi_ops, -#if !CONFIG_IS_ENABLED(OF_PLATDATA) .ofdata_to_platdata = pl022_spi_ofdata_to_platdata, #endif + .ops = &pl022_spi_ops, .platdata_auto_alloc_size = sizeof(struct pl022_spi_pdata), .priv_auto_alloc_size = sizeof(struct pl022_spi_slave), .probe = pl022_spi_probe, diff --git a/include/dm/platform_data/pl022_spi.h b/include/dm/platform_data/pl022_spi.h index 77fe6da3cb2..df8870169dd 100644 --- a/include/dm/platform_data/pl022_spi.h +++ b/include/dm/platform_data/pl022_spi.h @@ -10,19 +10,12 @@ #ifndef __PL022_SPI_H__ #define __PL022_SPI_H__ -#if !CONFIG_IS_ENABLED(OF_PLATDATA) -#include -#endif #include struct pl022_spi_pdata { fdt_addr_t addr; fdt_size_t size; -#if !CONFIG_IS_ENABLED(OF_PLATDATA) - struct clk clk; -#else unsigned int freq; -#endif }; #endif From e6f76d555e3b1d23dec3c9c7b73a51ea1ac3ec30 Mon Sep 17 00:00:00 2001 From: Jagan Teki Date: Thu, 22 Nov 2018 11:53:23 +0530 Subject: [PATCH 10/28] spi: pl022: Drop unnecessary include files This patch can drop unnecessary include files in pl022_spi driver. Acked-by: Quentin Schulz Signed-off-by: Jagan Teki --- drivers/spi/pl022_spi.c | 5 ----- 1 file changed, 5 deletions(-) diff --git a/drivers/spi/pl022_spi.c b/drivers/spi/pl022_spi.c index 05f4f6f4819..f2e53672255 100644 --- a/drivers/spi/pl022_spi.c +++ b/drivers/spi/pl022_spi.c @@ -9,16 +9,11 @@ * Driver for ARM PL022 SPI Controller. */ -#include #include #include #include #include -#include -#include -#include #include -#include #include #define SSP_CR0 0x000 From 3ae6030cf9587d310ee9cb8c3b17e9a8693f7636 Mon Sep 17 00:00:00 2001 From: Jagan Teki Date: Thu, 22 Nov 2018 11:54:08 +0530 Subject: [PATCH 11/28] dm: platform_data: spi: s/pl022_spi.h/spi_pl022.h Rename platform_data include file as spi_pl022.h from pl022_spi.h, this is generic notation used for spi platdata include files. Acked-by: Quentin Schulz Signed-off-by: Jagan Teki --- drivers/spi/pl022_spi.c | 2 +- include/dm/platform_data/{pl022_spi.h => spi_pl022.h} | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) rename include/dm/platform_data/{pl022_spi.h => spi_pl022.h} (82%) diff --git a/drivers/spi/pl022_spi.c b/drivers/spi/pl022_spi.c index f2e53672255..32bb8c8d212 100644 --- a/drivers/spi/pl022_spi.c +++ b/drivers/spi/pl022_spi.c @@ -12,7 +12,7 @@ #include #include #include -#include +#include #include #include diff --git a/include/dm/platform_data/pl022_spi.h b/include/dm/platform_data/spi_pl022.h similarity index 82% rename from include/dm/platform_data/pl022_spi.h rename to include/dm/platform_data/spi_pl022.h index df8870169dd..63a58ee4535 100644 --- a/include/dm/platform_data/pl022_spi.h +++ b/include/dm/platform_data/spi_pl022.h @@ -7,8 +7,8 @@ * in ofdata_to_platdata. */ -#ifndef __PL022_SPI_H__ -#define __PL022_SPI_H__ +#ifndef __spi_pl022_h +#define __spi_pl022_h #include @@ -18,4 +18,4 @@ struct pl022_spi_pdata { unsigned int freq; }; -#endif +#endif /* __spi_pl022_h */ From e2cae514725568bb4d3f8588c816f6ca521fa68f Mon Sep 17 00:00:00 2001 From: Jagan Teki Date: Tue, 20 Nov 2018 15:06:35 +0530 Subject: [PATCH 12/28] spi: Remove unused spi_init Remove spi_init definition which never used on respective code since from many years. Signed-off-by: Jagan Teki --- drivers/net/e1000_spi.c | 3 --- drivers/spi/atmel_spi.c | 5 ----- drivers/spi/davinci_spi.c | 5 ----- drivers/spi/fsl_dspi.c | 5 ----- drivers/spi/fsl_espi.c | 5 ----- drivers/spi/lpc32xx_ssp.c | 9 --------- drivers/spi/mxc_spi.c | 4 ---- drivers/spi/mxs_spi.c | 4 ---- drivers/spi/omap3_spi.c | 5 ----- drivers/spi/sh_qspi.c | 5 ----- drivers/spi/sh_spi.c | 4 ---- drivers/spi/soft_spi_legacy.c | 7 ------- include/spi.h | 7 ------- 13 files changed, 68 deletions(-) diff --git a/drivers/net/e1000_spi.c b/drivers/net/e1000_spi.c index b38f4df9f31..aecd290d729 100644 --- a/drivers/net/e1000_spi.c +++ b/drivers/net/e1000_spi.c @@ -77,9 +77,6 @@ static inline struct e1000_hw *e1000_hw_from_spi(struct spi_slave *spi) return container_of(spi, struct e1000_hw, spi); } -/* Not sure why all of these are necessary */ -void spi_init(void) { /* Nothing to do */ } - struct spi_slave *spi_setup_slave(unsigned int bus, unsigned int cs, unsigned int max_hz, unsigned int mode) { diff --git a/drivers/spi/atmel_spi.c b/drivers/spi/atmel_spi.c index 1db8bbef2bb..cf4de9ee1aa 100644 --- a/drivers/spi/atmel_spi.c +++ b/drivers/spi/atmel_spi.c @@ -34,11 +34,6 @@ static int spi_has_wdrbt(struct atmel_spi_slave *slave) return (ATMEL_SPI_VERSION_REV(ver) >= 0x210); } -void spi_init() -{ - -} - struct spi_slave *spi_setup_slave(unsigned int bus, unsigned int cs, unsigned int max_hz, unsigned int mode) { diff --git a/drivers/spi/davinci_spi.c b/drivers/spi/davinci_spi.c index 07fa5e3b8a1..4d2c106440b 100644 --- a/drivers/spi/davinci_spi.c +++ b/drivers/spi/davinci_spi.c @@ -388,11 +388,6 @@ void spi_cs_deactivate(struct spi_slave *slave) /* do nothing */ } -void spi_init(void) -{ - /* do nothing */ -} - struct spi_slave *spi_setup_slave(unsigned int bus, unsigned int cs, unsigned int max_hz, unsigned int mode) { diff --git a/drivers/spi/fsl_dspi.c b/drivers/spi/fsl_dspi.c index f7ed8fbe08b..764c94215e7 100644 --- a/drivers/spi/fsl_dspi.c +++ b/drivers/spi/fsl_dspi.c @@ -390,11 +390,6 @@ static int fsl_dspi_cfg_speed(struct fsl_dspi_priv *priv, uint speed) return 0; } #ifndef CONFIG_DM_SPI -void spi_init(void) -{ - /* Nothing to do */ -} - int spi_cs_is_valid(unsigned int bus, unsigned int cs) { if (((cs >= 0) && (cs < 8)) && ((bus >= 0) && (bus < 8))) diff --git a/drivers/spi/fsl_espi.c b/drivers/spi/fsl_espi.c index e9941593f5f..7444ae1a068 100644 --- a/drivers/spi/fsl_espi.c +++ b/drivers/spi/fsl_espi.c @@ -118,11 +118,6 @@ void spi_free_slave(struct spi_slave *slave) free(fsl); } -void spi_init(void) -{ - -} - int spi_claim_bus(struct spi_slave *slave) { struct fsl_spi_slave *fsl = to_fsl_spi_slave(slave); diff --git a/drivers/spi/lpc32xx_ssp.c b/drivers/spi/lpc32xx_ssp.c index ce12eee6571..4b09366317a 100644 --- a/drivers/spi/lpc32xx_ssp.c +++ b/drivers/spi/lpc32xx_ssp.c @@ -47,15 +47,6 @@ static inline struct lpc32xx_spi_slave *to_lpc32xx_spi_slave( return container_of(slave, struct lpc32xx_spi_slave, slave); } -/* spi_init is called during boot when CONFIG_CMD_SPI is defined */ -void spi_init(void) -{ - /* - * nothing to do: clocking was enabled in lpc32xx_ssp_enable() - * and configuration will be done in spi_setup_slave() - */ -} - /* the following is called in sequence by do_spi_xfer() */ struct spi_slave *spi_setup_slave(uint bus, uint cs, uint max_hz, uint mode) diff --git a/drivers/spi/mxc_spi.c b/drivers/spi/mxc_spi.c index 0dccc38b82d..b2636909ce6 100644 --- a/drivers/spi/mxc_spi.c +++ b/drivers/spi/mxc_spi.c @@ -400,10 +400,6 @@ int spi_xfer(struct spi_slave *slave, unsigned int bitlen, const void *dout, return mxc_spi_xfer_internal(mxcs, bitlen, dout, din, flags); } -void spi_init(void) -{ -} - /* * Some SPI devices require active chip-select over multiple * transactions, we achieve this using a GPIO. Still, the SPI diff --git a/drivers/spi/mxs_spi.c b/drivers/spi/mxs_spi.c index 006fe8281c5..5065e407f82 100644 --- a/drivers/spi/mxs_spi.c +++ b/drivers/spi/mxs_spi.c @@ -39,10 +39,6 @@ static inline struct mxs_spi_slave *to_mxs_slave(struct spi_slave *slave) return container_of(slave, struct mxs_spi_slave, slave); } -void spi_init(void) -{ -} - int spi_cs_is_valid(unsigned int bus, unsigned int cs) { /* MXS SPI: 4 ports and 3 chip selects maximum */ diff --git a/drivers/spi/omap3_spi.c b/drivers/spi/omap3_spi.c index ecf54bb7148..c7fcf050a58 100644 --- a/drivers/spi/omap3_spi.c +++ b/drivers/spi/omap3_spi.c @@ -461,11 +461,6 @@ static inline struct omap3_spi_priv *to_omap3_spi(struct spi_slave *slave) return container_of(slave, struct omap3_spi_priv, slave); } -void spi_init(void) -{ - /* do nothing */ -} - void spi_free_slave(struct spi_slave *slave) { struct omap3_spi_priv *priv = to_omap3_spi(slave); diff --git a/drivers/spi/sh_qspi.c b/drivers/spi/sh_qspi.c index 64dfd748d67..5ae203d8d4f 100644 --- a/drivers/spi/sh_qspi.c +++ b/drivers/spi/sh_qspi.c @@ -247,11 +247,6 @@ void spi_cs_deactivate(struct spi_slave *slave) sh_qspi_cs_deactivate(ss); } -void spi_init(void) -{ - /* nothing to do */ -} - struct spi_slave *spi_setup_slave(unsigned int bus, unsigned int cs, unsigned int max_hz, unsigned int mode) { diff --git a/drivers/spi/sh_spi.c b/drivers/spi/sh_spi.c index bc2bd638e6f..c58fd0ebc43 100644 --- a/drivers/spi/sh_spi.c +++ b/drivers/spi/sh_spi.c @@ -66,10 +66,6 @@ static int write_fifo_empty_wait(struct sh_spi *ss) return 0; } -void spi_init(void) -{ -} - static void sh_spi_set_cs(struct sh_spi *ss, unsigned int cs) { unsigned long val = 0; diff --git a/drivers/spi/soft_spi_legacy.c b/drivers/spi/soft_spi_legacy.c index 0aac0c065da..cc5ab5f991d 100644 --- a/drivers/spi/soft_spi_legacy.c +++ b/drivers/spi/soft_spi_legacy.c @@ -36,13 +36,6 @@ static inline struct soft_spi_slave *to_soft_spi(struct spi_slave *slave) /* Public Functions */ /*=====================================================================*/ -/*----------------------------------------------------------------------- - * Initialization - */ -void spi_init (void) -{ -} - struct spi_slave *spi_setup_slave(unsigned int bus, unsigned int cs, unsigned int max_hz, unsigned int mode) { diff --git a/include/spi.h b/include/spi.h index 938627bc012..92427e5f329 100644 --- a/include/spi.h +++ b/include/spi.h @@ -117,13 +117,6 @@ struct spi_slave { #define SPI_XFER_MMAP_END BIT(3) /* Memory Mapped End */ }; -/** - * Initialization, must be called once on start up. - * - * TODO: I don't think we really need this. - */ -void spi_init(void); - /** * spi_do_alloc_slave - Allocate a new SPI slave (internal) * From fe82ca8f7148295c1b04c02f20ea2ecde859c253 Mon Sep 17 00:00:00 2001 From: Jagan Teki Date: Tue, 20 Nov 2018 09:02:04 +0100 Subject: [PATCH 13/28] spi: Remove used spi_init spi_init used in some areas in tree, but the respective drivers will remove in future patches. So remove the same instances. Signed-off-by: Jagan Teki --- common/board_f.c | 2 -- doc/driver-model/spi-howto.txt | 5 ----- examples/standalone/atmel_df_pow2.c | 2 -- include/_exports.h | 2 -- 4 files changed, 11 deletions(-) diff --git a/common/board_f.c b/common/board_f.c index f1a1432d869..60634e52cd7 100644 --- a/common/board_f.c +++ b/common/board_f.c @@ -23,7 +23,6 @@ #include #include #include -#include #include #include #include @@ -262,7 +261,6 @@ __weak int init_func_vid(void) static int init_func_spi(void) { puts("SPI: "); - spi_init(); puts("ready\n"); return 0; } diff --git a/doc/driver-model/spi-howto.txt b/doc/driver-model/spi-howto.txt index 1955ffe284e..38c26f642bc 100644 --- a/doc/driver-model/spi-howto.txt +++ b/doc/driver-model/spi-howto.txt @@ -163,11 +163,6 @@ At this point you should be able to build U-Boot for your board with the empty SPI driver. You still have empty methods in your driver, but we will write these one by one. -If you have spi_init() functions or the like that are called from your -board then the build will fail. Remove these calls and make a note of the -init that needs to be done. - - 7. Set up your platform data structure This will hold the information your driver to operate, like its hardware diff --git a/examples/standalone/atmel_df_pow2.c b/examples/standalone/atmel_df_pow2.c index 2e14aba390e..b7bd243730b 100644 --- a/examples/standalone/atmel_df_pow2.c +++ b/examples/standalone/atmel_df_pow2.c @@ -126,8 +126,6 @@ int atmel_df_pow2(int argc, char * const argv[]) return 1; } - spi_init(); - while (1) { struct spi_slave *slave; char *line, *p; diff --git a/include/_exports.h b/include/_exports.h index 5416041243d..c15050e30b7 100644 --- a/include/_exports.h +++ b/include/_exports.h @@ -50,11 +50,9 @@ #endif #if !defined(CONFIG_CMD_SPI) || defined(CONFIG_DM_SPI) - EXPORT_FUNC(dummy, void, spi_init, void) EXPORT_FUNC(dummy, void, spi_setup_slave, void) EXPORT_FUNC(dummy, void, spi_free_slave, void) #else - EXPORT_FUNC(spi_init, void, spi_init, void) EXPORT_FUNC(spi_setup_slave, struct spi_slave *, spi_setup_slave, unsigned int, unsigned int, unsigned int, unsigned int) EXPORT_FUNC(spi_free_slave, void, spi_free_slave, struct spi_slave *) From f34d0315e9fec21baed198500d58d1613cf4d17c Mon Sep 17 00:00:00 2001 From: Christophe Leroy Date: Wed, 21 Nov 2018 08:51:57 +0000 Subject: [PATCH 14/28] spi: mpc8xx: Migrate to DM_SPI Drop non-dm code and migrate into DM_SPI. Signed-off-by: Christophe Leroy [jagan: Move config menu in DM_SPI area] Signed-off-by: Jagan Teki --- drivers/spi/Kconfig | 12 +-- drivers/spi/mpc8xx_spi.c | 179 +++++++-------------------------------- 2 files changed, 36 insertions(+), 155 deletions(-) diff --git a/drivers/spi/Kconfig b/drivers/spi/Kconfig index 4a8a38b863c..a7bb5b35c29 100644 --- a/drivers/spi/Kconfig +++ b/drivers/spi/Kconfig @@ -124,6 +124,12 @@ config MESON_SPIFC This driver can be used to access the SPI NOR flash chips on Amlogic Meson SoCs. +config MPC8XX_SPI + bool "MPC8XX SPI Driver" + depends on MPC8xx + help + Enable support for SPI on MPC8XX + config MT7621_SPI bool "MediaTek MT7621 SPI driver" depends on ARCH_MT7620 @@ -343,12 +349,6 @@ config LPC32XX_SSP help Enable support for SPI on LPC32xx -config MPC8XX_SPI - bool "MPC8XX SPI Driver" - depends on MPC8xx - help - Enable support for SPI on MPC8XX - config MPC8XXX_SPI bool "MPC8XXX SPI Driver" help diff --git a/drivers/spi/mpc8xx_spi.c b/drivers/spi/mpc8xx_spi.c index 285fd4d2cc0..b020ce2b9d7 100644 --- a/drivers/spi/mpc8xx_spi.c +++ b/drivers/spi/mpc8xx_spi.c @@ -17,64 +17,19 @@ */ #include +#include #include +#include + #include -#include -#include -#include -#include - -#define SPI_EEPROM_WREN 0x06 -#define SPI_EEPROM_RDSR 0x05 -#define SPI_EEPROM_READ 0x03 -#define SPI_EEPROM_WRITE 0x02 - -/* --------------------------------------------------------------- - * Offset for initial SPI buffers in DPRAM: - * We need a 520 byte scratch DPRAM area to use at an early stage. - * It is used between the two initialization calls (spi_init_f() - * and spi_init_r()). - * The value 0xb00 makes it far enough from the start of the data - * area (as well as from the stack pointer). - * --------------------------------------------------------------- */ -#ifndef CONFIG_SYS_SPI_INIT_OFFSET -#define CONFIG_SYS_SPI_INIT_OFFSET 0xB00 -#endif +#include #define CPM_SPI_BASE_RX CPM_SPI_BASE #define CPM_SPI_BASE_TX (CPM_SPI_BASE + sizeof(cbd_t)) -/* ------------------- - * Function prototypes - * ------------------- */ -ssize_t spi_xfer(size_t); - -/* ------------------- - * Variables - * ------------------- */ - #define MAX_BUFFER 0x104 -/* ---------------------------------------------------------------------- - * Initially we place the RX and TX buffers at a fixed location in DPRAM! - * ---------------------------------------------------------------------- */ -static uchar *rxbuf = - (uchar *)&((cpm8xx_t *)&((immap_t *)CONFIG_SYS_IMMR)->im_cpm)->cp_dpmem - [CONFIG_SYS_SPI_INIT_OFFSET]; -static uchar *txbuf = - (uchar *)&((cpm8xx_t *)&((immap_t *)CONFIG_SYS_IMMR)->im_cpm)->cp_dpmem - [CONFIG_SYS_SPI_INIT_OFFSET+MAX_BUFFER]; - -/* ************************************************************************** - * - * Function: spi_init_f - * - * Description: Init SPI-Controller (ROM part) - * - * return: --- - * - * *********************************************************************** */ -void spi_init_f(void) +static int mpc8xx_spi_probe(struct udevice *dev) { immap_t __iomem *immr = (immap_t __iomem *)CONFIG_SYS_IMMR; cpm8xx_t __iomem *cp = &immr->im_cpm; @@ -180,117 +135,24 @@ void spi_init_f(void) clrbits_be16(&tbdf->cbd_sc, BD_SC_READY); clrbits_be16(&rbdf->cbd_sc, BD_SC_EMPTY); - /* Set the bd's rx and tx buffer address pointers */ - out_be32(&rbdf->cbd_bufaddr, (ulong)rxbuf); - out_be32(&tbdf->cbd_bufaddr, (ulong)txbuf); - /* 10 + 11 */ out_8(&cp->cp_spim, 0); /* Mask all SPI events */ out_8(&cp->cp_spie, SPI_EMASK); /* Clear all SPI events */ - return; + return 0; } -/* ************************************************************************** - * - * Function: spi_init_r - * - * Description: Init SPI-Controller (RAM part) - - * The malloc engine is ready and we can move our buffers to - * normal RAM - * - * return: --- - * - * *********************************************************************** */ -void spi_init_r(void) +static int mpc8xx_spi_xfer(struct udevice *dev, unsigned int bitlen, + const void *dout, void *din, unsigned long flags) { immap_t __iomem *immr = (immap_t __iomem *)CONFIG_SYS_IMMR; cpm8xx_t __iomem *cp = &immr->im_cpm; - spi_t __iomem *spi = (spi_t __iomem *)&cp->cp_dparam[PROFF_SPI]; - cbd_t __iomem *tbdf, *rbdf; - - /* Disable relocation */ - out_be16(&spi->spi_rpbase, 0); - - /* tx and rx buffer descriptors */ - tbdf = (cbd_t __iomem *)&cp->cp_dpmem[CPM_SPI_BASE_TX]; - rbdf = (cbd_t __iomem *)&cp->cp_dpmem[CPM_SPI_BASE_RX]; - - /* Allocate memory for RX and TX buffers */ - rxbuf = (uchar *)malloc(MAX_BUFFER); - txbuf = (uchar *)malloc(MAX_BUFFER); - - out_be32(&rbdf->cbd_bufaddr, (ulong)rxbuf); - out_be32(&tbdf->cbd_bufaddr, (ulong)txbuf); - - return; -} - -/**************************************************************************** - * Function: spi_write - **************************************************************************** */ -ssize_t spi_write(uchar *addr, int alen, uchar *buffer, int len) -{ - int i; - - memset(rxbuf, 0, MAX_BUFFER); - memset(txbuf, 0, MAX_BUFFER); - *txbuf = SPI_EEPROM_WREN; /* write enable */ - spi_xfer(1); - memcpy(txbuf, addr, alen); - *txbuf = SPI_EEPROM_WRITE; /* WRITE memory array */ - memcpy(alen + txbuf, buffer, len); - spi_xfer(alen + len); - /* ignore received data */ - for (i = 0; i < 1000; i++) { - *txbuf = SPI_EEPROM_RDSR; /* read status */ - txbuf[1] = 0; - spi_xfer(2); - if (!(rxbuf[1] & 1)) - break; - udelay(1000); - } - if (i >= 1000) - printf("*** spi_write: Time out while writing!\n"); - - return len; -} - -/**************************************************************************** - * Function: spi_read - **************************************************************************** */ -ssize_t spi_read(uchar *addr, int alen, uchar *buffer, int len) -{ - memset(rxbuf, 0, MAX_BUFFER); - memset(txbuf, 0, MAX_BUFFER); - memcpy(txbuf, addr, alen); - *txbuf = SPI_EEPROM_READ; /* READ memory array */ - - /* - * There is a bug in 860T (?) that cuts the last byte of input - * if we're reading into DPRAM. The solution we choose here is - * to always read len+1 bytes (we have one extra byte at the - * end of the buffer). - */ - spi_xfer(alen + len + 1); - memcpy(buffer, alen + rxbuf, len); - - return len; -} - -/**************************************************************************** - * Function: spi_xfer - **************************************************************************** */ -ssize_t spi_xfer(size_t count) -{ - immap_t __iomem *immr = (immap_t __iomem *)CONFIG_SYS_IMMR; - cpm8xx_t __iomem *cp = &immr->im_cpm; - spi_t __iomem *spi = (spi_t __iomem *)&cp->cp_dparam[PROFF_SPI]; cbd_t __iomem *tbdf, *rbdf; int tm; + size_t count = (bitlen + 7) / 8; - /* Disable relocation */ - out_be16(&spi->spi_rpbase, 0); + if (count > MAX_BUFFER) + return -EINVAL; tbdf = (cbd_t __iomem *)&cp->cp_dpmem[CPM_SPI_BASE_TX]; rbdf = (cbd_t __iomem *)&cp->cp_dpmem[CPM_SPI_BASE_RX]; @@ -299,10 +161,12 @@ ssize_t spi_xfer(size_t count) clrbits_be32(&cp->cp_pbdat, 0x0001); /* Setting tx bd status and data length */ + out_be32(&tbdf->cbd_bufaddr, (ulong)dout); out_be16(&tbdf->cbd_sc, BD_SC_READY | BD_SC_LAST | BD_SC_WRAP); out_be16(&tbdf->cbd_datlen, count); /* Setting rx bd status and data length */ + out_be32(&rbdf->cbd_bufaddr, (ulong)din); out_be16(&rbdf->cbd_sc, BD_SC_EMPTY | BD_SC_WRAP); out_be16(&rbdf->cbd_datlen, 0); /* rx length has no significance */ @@ -333,3 +197,20 @@ ssize_t spi_xfer(size_t count) return count; } + +static const struct dm_spi_ops mpc8xx_spi_ops = { + .xfer = mpc8xx_spi_xfer, +}; + +static const struct udevice_id mpc8xx_spi_ids[] = { + { .compatible = "fsl,mpc8xx-spi" }, + { } +}; + +U_BOOT_DRIVER(mpc8xx_spi) = { + .name = "mpc8xx_spi", + .id = UCLASS_SPI, + .of_match = mpc8xx_spi_ids, + .ops = &mpc8xx_spi_ops, + .probe = mpc8xx_spi_probe, +}; From efbeabee79a28760837fcd5e75ff53d8bb857835 Mon Sep 17 00:00:00 2001 From: Jagan Teki Date: Thu, 22 Nov 2018 21:38:38 +0530 Subject: [PATCH 15/28] spi: Remove unused mpc8xx code - spi_init_f - spi_init_r - spi_read - spi_write these spi calls are exclusively for mpc8xx, but the relevant driver is not available so remove it. Signed-off-by: Jagan Teki --- cmd/eeprom.c | 15 +-------------- common/board_r.c | 7 ------- include/common.h | 7 ------- 3 files changed, 1 insertion(+), 28 deletions(-) diff --git a/cmd/eeprom.c b/cmd/eeprom.c index 4052cf494a5..9136630ada4 100644 --- a/cmd/eeprom.c +++ b/cmd/eeprom.c @@ -66,11 +66,6 @@ __weak int eeprom_write_enable(unsigned dev_addr, int state) void eeprom_init(int bus) { - /* SPI EEPROM */ -#if defined(CONFIG_MPC8XX_SPI) && !defined(CONFIG_ENV_EEPROM_IS_ON_I2C) - spi_init_f(); -#endif - /* I2C EEPROM */ #if defined(CONFIG_SYS_I2C) if (bus >= 0) @@ -129,14 +124,6 @@ static int eeprom_rw_block(unsigned offset, uchar *addr, unsigned alen, { int ret = 0; - /* SPI */ -#if defined(CONFIG_MPC8XX_SPI) && !defined(CONFIG_ENV_EEPROM_IS_ON_I2C) - if (read) - spi_read(addr, alen, buffer, len); - else - spi_write(addr, alen, buffer, len); -#else /* I2C */ - #if defined(CONFIG_SYS_I2C_EEPROM_BUS) i2c_set_bus_num(CONFIG_SYS_I2C_EEPROM_BUS); #endif @@ -148,7 +135,7 @@ static int eeprom_rw_block(unsigned offset, uchar *addr, unsigned alen, if (ret) ret = 1; -#endif + return ret; } diff --git a/common/board_r.c b/common/board_r.c index c55e33eec27..3ccef5467b9 100644 --- a/common/board_r.c +++ b/common/board_r.c @@ -382,13 +382,6 @@ static int initr_flash(void) #if defined(CONFIG_PPC) && !defined(CONFIG_DM_SPI) static int initr_spi(void) { - /* MPC8xx does this here */ -#ifdef CONFIG_MPC8XX_SPI -#if !defined(CONFIG_ENV_IS_IN_EEPROM) - spi_init_f(); -#endif - spi_init_r(); -#endif return 0; } #endif diff --git a/include/common.h b/include/common.h index 3f699438875..cfbdf7ebcb4 100644 --- a/include/common.h +++ b/include/common.h @@ -276,13 +276,6 @@ int eeprom_write (unsigned dev_addr, unsigned offset, uchar *buffer, unsigned c # define CONFIG_SYS_DEF_EEPROM_ADDR CONFIG_SYS_I2C_EEPROM_ADDR #endif -#if defined(CONFIG_MPC8XX_SPI) -extern void spi_init_f (void); -extern void spi_init_r (void); -extern ssize_t spi_read (uchar *, int, uchar *, int); -extern ssize_t spi_write (uchar *, int, uchar *, int); -#endif - /* $(BOARD)/$(BOARD).c */ int board_early_init_f (void); int board_fix_fdt (void *rw_fdt_blob); /* manipulate the U-Boot fdt before its relocation */ From 35f9d9bdd07d5e508272421b215ffaffd867bad8 Mon Sep 17 00:00:00 2001 From: Jagan Teki Date: Sat, 24 Nov 2018 14:31:12 +0530 Subject: [PATCH 16/28] spi: Zap CONFIG_HARD_SPI In legacy CONFIG_HARD_SPI initalizing spi_init code, which was removed during dm conversion cleanup. So remove the dead instances of CONFIG_HARD_SPI, and related code. Signed-off-by: Jagan Teki --- README | 8 -------- arch/powerpc/include/asm/config.h | 7 ------- board/freescale/mpc8349emds/mpc8349emds.c | 2 +- board/ids/ids8313/ids8313.c | 2 +- common/board_f.c | 12 ------------ include/configs/M52277EVB.h | 1 - include/configs/M54418TWR.h | 1 - include/configs/M54451EVB.h | 1 - include/configs/M54455EVB.h | 1 - include/configs/MPC8536DS.h | 5 ----- include/configs/P1022DS.h | 6 ------ include/configs/UCP1020.h | 5 ----- include/configs/controlcenterd.h | 4 ---- include/configs/dreamplug.h | 1 - include/configs/ds109.h | 1 - include/configs/ids8313.h | 10 ---------- include/configs/mx31pdk.h | 1 - include/configs/mxs.h | 1 - include/configs/p1_p2_rdb_pc.h | 5 ----- include/configs/p1_twr.h | 5 ----- include/configs/stmark2.h | 1 - include/configs/ts4800.h | 5 ----- scripts/config_whitelist.txt | 1 - 23 files changed, 2 insertions(+), 84 deletions(-) diff --git a/README b/README index a46c7c63a4f..17d56b80349 100644 --- a/README +++ b/README @@ -1932,14 +1932,6 @@ The following options need to be configured: SPI configuration items (port pins to use, etc). For an example, see include/configs/sacsng.h. - CONFIG_HARD_SPI - - Enables a hardware SPI driver for general-purpose reads - and writes. As with CONFIG_SOFT_SPI, the board configuration - must define a list of chip-select function pointers. - Currently supported on some MPC8xxx processors. For an - example, see include/configs/mpc8349emds.h. - CONFIG_SYS_SPI_MXC_WAIT Timeout for waiting until spi transfer completed. default: (CONFIG_SYS_HZ/100) /* 10 ms */ diff --git a/arch/powerpc/include/asm/config.h b/arch/powerpc/include/asm/config.h index 849a69acedc..c9c99646309 100644 --- a/arch/powerpc/include/asm/config.h +++ b/arch/powerpc/include/asm/config.h @@ -18,13 +18,6 @@ #define HWCONFIG_BUFFER_SIZE 256 #endif -/* CONFIG_HARD_SPI triggers SPI bus initialization in PowerPC */ -#if defined(CONFIG_MPC8XXX_SPI) || defined(CONFIG_FSL_ESPI) -# ifndef CONFIG_HARD_SPI -# define CONFIG_HARD_SPI -# endif -#endif - #define CONFIG_LMB #define CONFIG_SYS_BOOT_RAMDISK_HIGH diff --git a/board/freescale/mpc8349emds/mpc8349emds.c b/board/freescale/mpc8349emds/mpc8349emds.c index 4ec0af4d1c5..d40ed3742e0 100644 --- a/board/freescale/mpc8349emds/mpc8349emds.c +++ b/board/freescale/mpc8349emds/mpc8349emds.c @@ -273,7 +273,7 @@ void spi_cs_deactivate(struct spi_slave *slave) iopd->dat |= SPI_CS_MASK; } -#endif /* CONFIG_HARD_SPI */ +#endif #if defined(CONFIG_OF_BOARD_SETUP) int ft_board_setup(void *blob, bd_t *bd) diff --git a/board/ids/ids8313/ids8313.c b/board/ids/ids8313/ids8313.c index a411d4e7f6f..d547af4b05a 100644 --- a/board/ids/ids8313/ids8313.c +++ b/board/ids/ids8313/ids8313.c @@ -208,4 +208,4 @@ void spi_cs_deactivate(struct spi_slave *slave) /* deactivate the spi_cs */ setbits_be32(&iopd->dat, IDSCPLD_SPI_CS_MASK); } -#endif /* CONFIG_HARD_SPI */ +#endif diff --git a/common/board_f.c b/common/board_f.c index 60634e52cd7..a3e80ca9512 100644 --- a/common/board_f.c +++ b/common/board_f.c @@ -257,15 +257,6 @@ __weak int init_func_vid(void) } #endif -#if defined(CONFIG_HARD_SPI) -static int init_func_spi(void) -{ - puts("SPI: "); - puts("ready\n"); - return 0; -} -#endif - static int setup_mon_len(void) { #if defined(__ARM__) || defined(__MICROBLAZE__) @@ -863,9 +854,6 @@ static const init_fnc_t init_sequence_f[] = { #endif #if defined(CONFIG_VID) && !defined(CONFIG_SPL) init_func_vid, -#endif -#if defined(CONFIG_HARD_SPI) - init_func_spi, #endif announce_dram_init, dram_init, /* configure available RAM banks */ diff --git a/include/configs/M52277EVB.h b/include/configs/M52277EVB.h index 11cb3955da5..83d774527ad 100644 --- a/include/configs/M52277EVB.h +++ b/include/configs/M52277EVB.h @@ -102,7 +102,6 @@ /* DSPI and Serial Flash */ #define CONFIG_CF_DSPI -#define CONFIG_HARD_SPI #define CONFIG_SYS_SBFHDR_SIZE 0x7 #ifdef CONFIG_CMD_SPI # define CONFIG_SYS_DSPI_CS2 diff --git a/include/configs/M54418TWR.h b/include/configs/M54418TWR.h index f08896ef0a0..4b8ef38c0be 100644 --- a/include/configs/M54418TWR.h +++ b/include/configs/M54418TWR.h @@ -151,7 +151,6 @@ /* DSPI and Serial Flash */ #define CONFIG_CF_DSPI #define CONFIG_SERIAL_FLASH -#define CONFIG_HARD_SPI #define CONFIG_SYS_SBFHDR_SIZE 0x7 #ifdef CONFIG_CMD_SPI diff --git a/include/configs/M54451EVB.h b/include/configs/M54451EVB.h index 16becbdbedd..87cdbae1dbf 100644 --- a/include/configs/M54451EVB.h +++ b/include/configs/M54451EVB.h @@ -116,7 +116,6 @@ /* DSPI and Serial Flash */ #define CONFIG_CF_DSPI #define CONFIG_SERIAL_FLASH -#define CONFIG_HARD_SPI #define CONFIG_SYS_SBFHDR_SIZE 0x7 #ifdef CONFIG_CMD_SPI diff --git a/include/configs/M54455EVB.h b/include/configs/M54455EVB.h index 99b60d5d823..d41b7c44929 100644 --- a/include/configs/M54455EVB.h +++ b/include/configs/M54455EVB.h @@ -142,7 +142,6 @@ /* DSPI and Serial Flash */ #define CONFIG_CF_DSPI -#define CONFIG_HARD_SPI #define CONFIG_SYS_SBFHDR_SIZE 0x13 #ifdef CONFIG_CMD_SPI diff --git a/include/configs/MPC8536DS.h b/include/configs/MPC8536DS.h index 524a10fc95a..86a1233e322 100644 --- a/include/configs/MPC8536DS.h +++ b/include/configs/MPC8536DS.h @@ -370,11 +370,6 @@ #define CONFIG_SYS_I2C_EEPROM_ADDR_LEN 1 #define CONFIG_SYS_EEPROM_BUS_NUM 1 -/* - * eSPI - Enhanced SPI - */ -#define CONFIG_HARD_SPI - #if defined(CONFIG_SPI_FLASH) #define CONFIG_SF_DEFAULT_SPEED 10000000 #define CONFIG_SF_DEFAULT_MODE 0 diff --git a/include/configs/P1022DS.h b/include/configs/P1022DS.h index c9ed70ca4cb..eeb19a9fa68 100644 --- a/include/configs/P1022DS.h +++ b/include/configs/P1022DS.h @@ -386,12 +386,6 @@ #define CONFIG_SYS_I2C_EEPROM_ADDR_LEN 1 #define CONFIG_SYS_EEPROM_BUS_NUM 1 -/* - * eSPI - Enhanced SPI - */ - -#define CONFIG_HARD_SPI - #define CONFIG_SF_DEFAULT_SPEED 10000000 #define CONFIG_SF_DEFAULT_MODE 0 diff --git a/include/configs/UCP1020.h b/include/configs/UCP1020.h index 423ecd71c27..1bbe9d9b375 100644 --- a/include/configs/UCP1020.h +++ b/include/configs/UCP1020.h @@ -287,11 +287,6 @@ #define CONFIG_SYS_I2C_NCT72_ADDR 0x4C #define CONFIG_SYS_I2C_IDT6V49205B 0x69 -/* - * eSPI - Enhanced SPI - */ -#define CONFIG_HARD_SPI - #define CONFIG_SF_DEFAULT_SPEED 10000000 #define CONFIG_SF_DEFAULT_MODE SPI_MODE_0 diff --git a/include/configs/controlcenterd.h b/include/configs/controlcenterd.h index 4adcd956efb..1908d35bcc6 100644 --- a/include/configs/controlcenterd.h +++ b/include/configs/controlcenterd.h @@ -182,10 +182,6 @@ #define CONFIG_SYS_I2C_EEPROM_ADDR_LEN 2 #ifndef CONFIG_TRAILBLAZER -/* - * eSPI - Enhanced SPI - */ -#define CONFIG_HARD_SPI #define CONFIG_SF_DEFAULT_SPEED 10000000 #define CONFIG_SF_DEFAULT_MODE 0 diff --git a/include/configs/dreamplug.h b/include/configs/dreamplug.h index 1c94bf9fa1b..f4d717213ce 100644 --- a/include/configs/dreamplug.h +++ b/include/configs/dreamplug.h @@ -35,7 +35,6 @@ #endif #ifdef CONFIG_CMD_SF -#define CONFIG_HARD_SPI 1 #define CONFIG_ENV_SPI_BUS 0 #define CONFIG_ENV_SPI_CS 0 #define CONFIG_ENV_SPI_MAX_HZ 50000000 /* 50 MHz */ diff --git a/include/configs/ds109.h b/include/configs/ds109.h index c06f0058deb..2c7928e88c1 100644 --- a/include/configs/ds109.h +++ b/include/configs/ds109.h @@ -38,7 +38,6 @@ #endif #ifdef CONFIG_CMD_SF -#define CONFIG_HARD_SPI 1 #define CONFIG_ENV_SPI_BUS 0 #define CONFIG_ENV_SPI_CS 0 #define CONFIG_ENV_SPI_MAX_HZ 50000000 /* 50 MHz */ diff --git a/include/configs/ids8313.h b/include/configs/ids8313.h index 28124dd4b12..7e4c497fe0a 100644 --- a/include/configs/ids8313.h +++ b/include/configs/ids8313.h @@ -159,7 +159,6 @@ */ #define CONFIG_TSEC1 #define CONFIG_TSEC2 -#define CONFIG_HARD_SPI /* * NOR FLASH setup @@ -273,15 +272,6 @@ #define CONFIG_RTC_PCF8563 #define CONFIG_SYS_I2C_RTC_ADDR 0x51 -/* - * SPI setup - */ -#ifdef CONFIG_HARD_SPI -#define CONFIG_SYS_GPIO1_PRELIM -#define CONFIG_SYS_GPIO1_DIR 0x00000001 -#define CONFIG_SYS_GPIO1_DAT 0x00000001 -#endif - /* * Ethernet setup */ diff --git a/include/configs/mx31pdk.h b/include/configs/mx31pdk.h index 7d84d160b4c..4765764f83a 100644 --- a/include/configs/mx31pdk.h +++ b/include/configs/mx31pdk.h @@ -43,7 +43,6 @@ #define CONFIG_MXC_UART #define CONFIG_MXC_UART_BASE UART1_BASE -#define CONFIG_HARD_SPI #define CONFIG_DEFAULT_SPI_BUS 1 #define CONFIG_DEFAULT_SPI_MODE (SPI_MODE_0 | SPI_CS_HIGH) diff --git a/include/configs/mxs.h b/include/configs/mxs.h index 9e59e7a4dcb..4bb3621a428 100644 --- a/include/configs/mxs.h +++ b/include/configs/mxs.h @@ -143,7 +143,6 @@ /* SPI */ #ifdef CONFIG_CMD_SPI -#define CONFIG_HARD_SPI #define CONFIG_SPI_HALF_DUPLEX #endif diff --git a/include/configs/p1_p2_rdb_pc.h b/include/configs/p1_p2_rdb_pc.h index 9465fb47027..459ecf328f2 100644 --- a/include/configs/p1_p2_rdb_pc.h +++ b/include/configs/p1_p2_rdb_pc.h @@ -576,11 +576,6 @@ #define CONFIG_SYS_EEPROM_PAGE_WRITE_BITS 3 #define CONFIG_SYS_EEPROM_PAGE_WRITE_DELAY_MS 5 -/* - * eSPI - Enhanced SPI - */ -#define CONFIG_HARD_SPI - #if defined(CONFIG_SPI_FLASH) #define CONFIG_SF_DEFAULT_SPEED 10000000 #define CONFIG_SF_DEFAULT_MODE 0 diff --git a/include/configs/p1_twr.h b/include/configs/p1_twr.h index d018c22afdb..4f483706488 100644 --- a/include/configs/p1_twr.h +++ b/include/configs/p1_twr.h @@ -214,11 +214,6 @@ extern unsigned long get_board_sys_clk(unsigned long dummy); #define CONFIG_SYS_EEPROM_PAGE_WRITE_BITS 3 #define CONFIG_SYS_EEPROM_PAGE_WRITE_DELAY_MS 5 -/* - * eSPI - Enhanced SPI - */ -#define CONFIG_HARD_SPI - #if defined(CONFIG_PCI) /* * General PCI diff --git a/include/configs/stmark2.h b/include/configs/stmark2.h index c408db865eb..33ddc67bf4a 100644 --- a/include/configs/stmark2.h +++ b/include/configs/stmark2.h @@ -66,7 +66,6 @@ #define CONFIG_CF_DSPI #define CONFIG_SF_DEFAULT_SPEED 50000000 #define CONFIG_SERIAL_FLASH -#define CONFIG_HARD_SPI #define CONFIG_ENV_SPI_BUS 0 #define CONFIG_ENV_SPI_CS 1 diff --git a/include/configs/ts4800.h b/include/configs/ts4800.h index 956f7795f1f..4e274bd4141 100644 --- a/include/configs/ts4800.h +++ b/include/configs/ts4800.h @@ -42,11 +42,6 @@ #define CONFIG_MXC_UART #define CONFIG_MXC_UART_BASE UART1_BASE -/* - * SPI Configs - * */ -#define CONFIG_HARD_SPI /* puts SPI: ready */ - /* * MMC Configs * */ diff --git a/scripts/config_whitelist.txt b/scripts/config_whitelist.txt index abfb0ff89fa..1404fd855bf 100644 --- a/scripts/config_whitelist.txt +++ b/scripts/config_whitelist.txt @@ -750,7 +750,6 @@ CONFIG_G_DNL_UMS_PRODUCT_NUM CONFIG_G_DNL_UMS_VENDOR_NUM CONFIG_H264_FREQ CONFIG_H8300 -CONFIG_HARD_SPI CONFIG_HAS_ETH0 CONFIG_HAS_ETH1 CONFIG_HAS_ETH2 From ef6ce46ad889d935dce26133232943aa302bd901 Mon Sep 17 00:00:00 2001 From: Jagan Teki Date: Wed, 21 Nov 2018 12:14:07 +0530 Subject: [PATCH 17/28] board_r: Remove initr_spi Drop unused initr_spi, which just return 0, no usage. Signed-off-by: Jagan Teki --- common/board_r.c | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/common/board_r.c b/common/board_r.c index 3ccef5467b9..2f982b16460 100644 --- a/common/board_r.c +++ b/common/board_r.c @@ -36,7 +36,6 @@ #include #include #include -#include #include #include #include @@ -379,13 +378,6 @@ static int initr_flash(void) } #endif -#if defined(CONFIG_PPC) && !defined(CONFIG_DM_SPI) -static int initr_spi(void) -{ - return 0; -} -#endif - #ifdef CONFIG_CMD_NAND /* go init the NAND */ static int initr_nand(void) @@ -736,9 +728,6 @@ static init_fnc_t init_sequence_r[] = { /* initialize higher level parts of CPU like time base and timers */ cpu_init_r, #endif -#ifdef CONFIG_PPC - initr_spi, -#endif #ifdef CONFIG_CMD_NAND initr_nand, #endif From 4c47fd0b6bce62162e11b8a22e2eaf0d8f6673b1 Mon Sep 17 00:00:00 2001 From: Boris Brezillon Date: Sun, 2 Dec 2018 10:54:22 +0100 Subject: [PATCH 18/28] mtd: Add a function to report when the MTD dev list has been updated We need to parse mtdparts/mtids again everytime a device has been added/removed from the MTD list, but there's currently no way to know when such an update has been done. Add an ->updated field to the idr struct that we set to true every time a device is added/removed and expose a function returning the value of this field and resetting it to false. Signed-off-by: Boris Brezillon Tested-by: Heiko Schocher --- drivers/mtd/mtdcore.c | 16 +++++++++++++++- include/linux/mtd/mtd.h | 1 + 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/drivers/mtd/mtdcore.c b/drivers/mtd/mtdcore.c index fb6c779abbf..7a15ded8c88 100644 --- a/drivers/mtd/mtdcore.c +++ b/drivers/mtd/mtdcore.c @@ -87,14 +87,17 @@ struct idr_layer { struct idr { struct idr_layer id[MAX_IDR_ID]; + bool updated; }; #define DEFINE_IDR(name) struct idr name; void idr_remove(struct idr *idp, int id) { - if (idp->id[id].used) + if (idp->id[id].used) { idp->id[id].used = 0; + idp->updated = true; + } return; } @@ -134,6 +137,7 @@ int idr_alloc(struct idr *idp, void *ptr, int start, int end, gfp_t gfp_mask) if (idl->used == 0) { idl->used = 1; idl->ptr = ptr; + idp->updated = true; return i; } i++; @@ -155,6 +159,16 @@ struct mtd_info *__mtd_next_device(int i) } EXPORT_SYMBOL_GPL(__mtd_next_device); +bool mtd_dev_list_updated(void) +{ + if (mtd_idr.updated) { + mtd_idr.updated = false; + return true; + } + + return false; +} + #ifndef __UBOOT__ static LIST_HEAD(mtd_notifiers); diff --git a/include/linux/mtd/mtd.h b/include/linux/mtd/mtd.h index 68e59153249..d20ebd82028 100644 --- a/include/linux/mtd/mtd.h +++ b/include/linux/mtd/mtd.h @@ -581,6 +581,7 @@ int mtd_arg_off_size(int argc, char *const argv[], int *idx, loff_t *off, void mtd_get_len_incl_bad(struct mtd_info *mtd, uint64_t offset, const uint64_t length, uint64_t *len_incl_bad, int *truncated); +bool mtd_dev_list_updated(void); /* drivers/mtd/mtd_uboot.c */ int mtd_search_alternate_name(const char *mtdname, char *altname, From 779c9c0565a44e7dc5f72919d88f67fb7e280880 Mon Sep 17 00:00:00 2001 From: Boris Brezillon Date: Sun, 2 Dec 2018 10:54:23 +0100 Subject: [PATCH 19/28] mtd: Parse mtdparts/mtdids again when the MTD list has been updated Updates to the MTD device list should trigger a new parsing of the mtdids/mtdparts vars even if those vars haven't changed. Fixes: 5db66b3aee6f ("cmd: mtd: add 'mtd' command") Signed-off-by: Boris Brezillon Tested-by: Heiko Schocher --- drivers/mtd/mtd_uboot.c | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/drivers/mtd/mtd_uboot.c b/drivers/mtd/mtd_uboot.c index 5ca560c9687..6a3e64395de 100644 --- a/drivers/mtd/mtd_uboot.c +++ b/drivers/mtd/mtd_uboot.c @@ -161,9 +161,13 @@ int mtd_probe_devices(void) mtd_probe_uclass_mtd_devs(); - /* Check if mtdparts/mtdids changed since last call, otherwise: exit */ + /* + * Check if mtdparts/mtdids changed or if the MTD dev list was updated + * since last call, otherwise: exit + */ if ((!mtdparts && !old_mtdparts && !mtdids && !old_mtdids) || (mtdparts && old_mtdparts && mtdids && old_mtdids && + !mtd_dev_list_updated() && !strcmp(mtdparts, old_mtdparts) && !strcmp(mtdids, old_mtdids))) return 0; @@ -201,6 +205,12 @@ int mtd_probe_devices(void) } } + /* + * Call mtd_dev_list_updated() to clear updates generated by our own + * parts removal loop. + */ + mtd_dev_list_updated(); + /* If either mtdparts or mtdids is empty, then exit */ if (!mtdparts || !mtdids) return 0; @@ -281,6 +291,12 @@ int mtd_probe_devices(void) put_mtd_device(mtd); } + /* + * Call mtd_dev_list_updated() to clear updates generated by our own + * parts registration loop. + */ + mtd_dev_list_updated(); + return 0; } #else From a02820fca90ce9ccf243b3fce59c04dabd5671a8 Mon Sep 17 00:00:00 2001 From: Boris Brezillon Date: Sun, 2 Dec 2018 10:54:24 +0100 Subject: [PATCH 20/28] mtd: Delete partitions attached to the device when a device is deleted If we don't do that, partitions might still be exposed while the underlying device is gone. Fixes: 2a74930da57f ("mtd: mtdpart: implement proper partition handling") Signed-off-by: Boris Brezillon Tested-by: Heiko Schocher --- drivers/mtd/mtdcore.c | 7 +++++++ include/linux/mtd/mtd.h | 15 +++++++++++++++ 2 files changed, 22 insertions(+) diff --git a/drivers/mtd/mtdcore.c b/drivers/mtd/mtdcore.c index 7a15ded8c88..cb7ca38d074 100644 --- a/drivers/mtd/mtdcore.c +++ b/drivers/mtd/mtdcore.c @@ -528,6 +528,13 @@ int del_mtd_device(struct mtd_info *mtd) struct mtd_notifier *not; #endif + ret = del_mtd_partitions(mtd); + if (ret) { + debug("Failed to delete MTD partitions attached to %s (err %d)\n", + mtd->name, ret); + return ret; + } + mutex_lock(&mtd_table_mutex); if (idr_find(&mtd_idr, mtd->index) != mtd) { diff --git a/include/linux/mtd/mtd.h b/include/linux/mtd/mtd.h index d20ebd82028..4d0096d9f1d 100644 --- a/include/linux/mtd/mtd.h +++ b/include/linux/mtd/mtd.h @@ -562,8 +562,23 @@ unsigned mtd_mmap_capabilities(struct mtd_info *mtd); /* drivers/mtd/mtdcore.h */ int add_mtd_device(struct mtd_info *mtd); int del_mtd_device(struct mtd_info *mtd); + +#ifdef CONFIG_MTD_PARTITIONS int add_mtd_partitions(struct mtd_info *, const struct mtd_partition *, int); int del_mtd_partitions(struct mtd_info *); +#else +static inline int add_mtd_partitions(struct mtd_info *mtd, + const struct mtd_partition *parts, + int nparts) +{ + return 0; +} + +static inline int del_mtd_partitions(struct mtd_info *mtd) +{ + return 0; +} +#endif struct mtd_info *__mtd_next_device(int i); #define mtd_for_each_device(mtd) \ From 492151b2093ee4df173f7e10da4938ef7c2a2156 Mon Sep 17 00:00:00 2001 From: Boris Brezillon Date: Sun, 2 Dec 2018 10:54:25 +0100 Subject: [PATCH 21/28] mtd: sf: Make sure we don't register the same device twice spi_flash_mtd_register() can be called several times and each time it will register the same mtd_info instance like if it was a new one. The MTD ID allocation gets crazy when that happens, so let's track the status of the sf_mtd_info object to avoid that. Fixes: 9fe6d8716e09 ("mtd, spi: Add MTD layer driver") Signed-off-by: Boris Brezillon Tested-by: Heiko Schocher Reviewed-by: Jagan Teki --- drivers/mtd/spi/sf_mtd.c | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/drivers/mtd/spi/sf_mtd.c b/drivers/mtd/spi/sf_mtd.c index 58d7e443990..aabbc358943 100644 --- a/drivers/mtd/spi/sf_mtd.c +++ b/drivers/mtd/spi/sf_mtd.c @@ -10,6 +10,7 @@ #include static struct mtd_info sf_mtd_info; +static bool sf_mtd_registered; static char sf_mtd_name[8]; static int spi_flash_mtd_erase(struct mtd_info *mtd, struct erase_info *instr) @@ -73,6 +74,12 @@ static int spi_flash_mtd_number(void) int spi_flash_mtd_register(struct spi_flash *flash) { + int ret; + + if (sf_mtd_registered) + del_mtd_device(&sf_mtd_info); + + sf_mtd_registered = false; memset(&sf_mtd_info, 0, sizeof(sf_mtd_info)); sprintf(sf_mtd_name, "nor%d", spi_flash_mtd_number()); @@ -94,7 +101,11 @@ int spi_flash_mtd_register(struct spi_flash *flash) sf_mtd_info.numeraseregions = 0; sf_mtd_info.erasesize = flash->sector_size; - return add_mtd_device(&sf_mtd_info); + ret = add_mtd_device(&sf_mtd_info); + if (!ret) + sf_mtd_registered = true; + + return ret; } void spi_flash_mtd_unregister(void) From 96b06434e56ce5dca391e8417d293132ff7164e0 Mon Sep 17 00:00:00 2001 From: Boris Brezillon Date: Sun, 2 Dec 2018 10:54:26 +0100 Subject: [PATCH 22/28] mtd: Use get_mtdids() instead of env_get("mtdids") in mtd_search_alternate_name() The environment is not guaranteed to contain a valid mtdids variable when called from mtd_search_alternate_name(). Call get_mtdids() instead of env_get("mtdids"). Fixes: ff4afa8a981e ("mtd: uboot: search for an equivalent MTD name with the mtdids") Signed-off-by: Boris Brezillon Reviewed-by: Miquel Raynal Tested-by: Heiko Schocher --- drivers/mtd/mtd_uboot.c | 49 ++++++++++++++++++++--------------------- 1 file changed, 24 insertions(+), 25 deletions(-) diff --git a/drivers/mtd/mtd_uboot.c b/drivers/mtd/mtd_uboot.c index 6a3e64395de..c4434d70520 100644 --- a/drivers/mtd/mtd_uboot.c +++ b/drivers/mtd/mtd_uboot.c @@ -13,6 +13,29 @@ #define MTD_NAME_MAX_LEN 20 +void board_mtdparts_default(const char **mtdids, const char **mtdparts); + +static const char *get_mtdids(void) +{ + __maybe_unused const char *mtdparts = NULL; + const char *mtdids = env_get("mtdids"); + + if (mtdids) + return mtdids; + +#if defined(CONFIG_SYS_MTDPARTS_RUNTIME) + board_mtdparts_default(&mtdids, &mtdparts); +#elif defined(MTDIDS_DEFAULT) + mtdids = MTDIDS_DEFAULT; +#elif defined(CONFIG_MTDIDS_DEFAULT) + mtdids = CONFIG_MTDIDS_DEFAULT; +#endif + + if (mtdids) + env_set("mtdids", mtdids); + + return mtdids; +} /** * mtd_search_alternate_name - Search an alternate name for @mtdname thanks to @@ -34,7 +57,7 @@ int mtd_search_alternate_name(const char *mtdname, char *altname, const char *mtdids, *equal, *comma, *dev_id, *mtd_id; int dev_id_len, mtd_id_len; - mtdids = env_get("mtdids"); + mtdids = get_mtdids(); if (!mtdids) return -EINVAL; @@ -92,30 +115,6 @@ static void mtd_probe_uclass_mtd_devs(void) { } #endif #if defined(CONFIG_MTD_PARTITIONS) -extern void board_mtdparts_default(const char **mtdids, - const char **mtdparts); - -static const char *get_mtdids(void) -{ - __maybe_unused const char *mtdparts = NULL; - const char *mtdids = env_get("mtdids"); - - if (mtdids) - return mtdids; - -#if defined(CONFIG_SYS_MTDPARTS_RUNTIME) - board_mtdparts_default(&mtdids, &mtdparts); -#elif defined(MTDIDS_DEFAULT) - mtdids = MTDIDS_DEFAULT; -#elif defined(CONFIG_MTDIDS_DEFAULT) - mtdids = CONFIG_MTDIDS_DEFAULT; -#endif - - if (mtdids) - env_set("mtdids", mtdids); - - return mtdids; -} #define MTDPARTS_MAXLEN 512 From 429e048e4190b68b078c37c0012c59804e32818b Mon Sep 17 00:00:00 2001 From: Boris Brezillon Date: Sun, 2 Dec 2018 10:54:27 +0100 Subject: [PATCH 23/28] mtd: Be more strict on the "mtdparts=" prefix check strstr() does not guarantee that the string we're searching for is placed at the beginning. Use strncmp() instead. Fixes: 5db66b3aee6f ("cmd: mtd: add 'mtd' command") Signed-off-by: Boris Brezillon Tested-by: Heiko Schocher --- drivers/mtd/mtd_uboot.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/mtd/mtd_uboot.c b/drivers/mtd/mtd_uboot.c index c4434d70520..d551aee2020 100644 --- a/drivers/mtd/mtd_uboot.c +++ b/drivers/mtd/mtd_uboot.c @@ -215,7 +215,7 @@ int mtd_probe_devices(void) return 0; /* Start the parsing by ignoring the extra 'mtdparts=' prefix, if any */ - if (strstr(mtdparts, "mtdparts=")) + if (!strncmp(mtdparts, "mtdparts=", sizeof("mtdparts=") - 1)) mtdparts += 9; /* For each MTD device in mtdparts */ From 772aa9799353d1d6bd6d9e2682945d4d7122539a Mon Sep 17 00:00:00 2001 From: Boris Brezillon Date: Sun, 2 Dec 2018 10:54:28 +0100 Subject: [PATCH 24/28] mtd: Make sure the name passed in mtdparts fits in mtd_name[] The local mtd_name[] variable is limited in size. Return an error if the name passed in mtdparts does not fit in this local var. Fixes: 5db66b3aee6f ("cmd: mtd: add 'mtd' command") Signed-off-by: Boris Brezillon Tested-by: Heiko Schocher --- drivers/mtd/mtd_uboot.c | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/drivers/mtd/mtd_uboot.c b/drivers/mtd/mtd_uboot.c index d551aee2020..0eda3627830 100644 --- a/drivers/mtd/mtd_uboot.c +++ b/drivers/mtd/mtd_uboot.c @@ -222,8 +222,8 @@ int mtd_probe_devices(void) while (mtdparts[0] != '\0') { char mtd_name[MTD_NAME_MAX_LEN], *colon; struct mtd_partition *parts; - int mtd_name_len, nparts; - int ret; + unsigned int mtd_name_len; + int nparts, ret; colon = strchr(mtdparts, ':'); if (!colon) { @@ -231,7 +231,12 @@ int mtd_probe_devices(void) return -EINVAL; } - mtd_name_len = colon - mtdparts; + mtd_name_len = (unsigned int)(colon - mtdparts); + if (mtd_name_len + 1 > sizeof(mtd_name)) { + printf("MTD name too long: %s\n", mtdparts); + return -EINVAL; + } + strncpy(mtd_name, mtdparts, mtd_name_len); mtd_name[mtd_name_len] = '\0'; /* Move the pointer forward (including the ':') */ From 2428d9160b80375870c1fff6cbb0214639628282 Mon Sep 17 00:00:00 2001 From: Boris Brezillon Date: Sun, 2 Dec 2018 10:54:29 +0100 Subject: [PATCH 25/28] mtd: Make sure we don't parse MTD partitions belonging to another dev The mtdparts variable might contain partition definitions for several MTD devices. Each partition layout is separated by a ';', so let's make sure we don't pick a wrong name when mtdparts is malformed. Fixes: 5db66b3aee6f ("cmd: mtd: add 'mtd' command") Signed-off-by: Boris Brezillon Tested-by: Heiko Schocher --- drivers/mtd/mtd_uboot.c | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/drivers/mtd/mtd_uboot.c b/drivers/mtd/mtd_uboot.c index 0eda3627830..6a36948b917 100644 --- a/drivers/mtd/mtd_uboot.c +++ b/drivers/mtd/mtd_uboot.c @@ -155,6 +155,7 @@ int mtd_probe_devices(void) static char *old_mtdids; const char *mtdparts = get_mtdparts(); const char *mtdids = get_mtdids(); + const char *mtdparts_next = mtdparts; bool remaining_partitions = true; struct mtd_info *mtd; @@ -219,13 +220,22 @@ int mtd_probe_devices(void) mtdparts += 9; /* For each MTD device in mtdparts */ - while (mtdparts[0] != '\0') { + for (; mtdparts[0] != '\0'; mtdparts = mtdparts_next) { char mtd_name[MTD_NAME_MAX_LEN], *colon; struct mtd_partition *parts; unsigned int mtd_name_len; int nparts, ret; + mtdparts_next = strchr(mtdparts, ';'); + if (!mtdparts_next) + mtdparts_next = mtdparts + strlen(mtdparts); + else + mtdparts_next++; + colon = strchr(mtdparts, ':'); + if (colon > mtdparts_next) + colon = NULL; + if (!colon) { printf("Wrong mtdparts: %s\n", mtdparts); return -EINVAL; @@ -263,10 +273,7 @@ int mtd_probe_devices(void) if (ret || IS_ERR_OR_NULL(mtd)) { printf("Could not find a valid device for %s\n", mtd_name); - mtdparts = strchr(mtdparts, ';'); - if (mtdparts) - mtdparts++; - + mtdparts = mtdparts_next; continue; } } From 4a5594fa20d0fa6479f477d2bd67967aca201c2f Mon Sep 17 00:00:00 2001 From: Boris Brezillon Date: Sun, 2 Dec 2018 10:54:30 +0100 Subject: [PATCH 26/28] mtd: Don't stop MTD partition creation when it fails on one device MTD partition creation code is a bit tricky. It tries to figure out when things have changed (either MTD dev list or mtdparts/mtdids vars) and when that happens it first deletes all the partitions that had been previously created and then creates the new ones based on the new mtdparts/mtdids values. But before deleting the old partitions, it ensures that none of the currently registered parts are being used and bails out when that's not the case. So, we end up in a situation where, if at least one MTD dev has one of its partitions used by someone (UBI for instance), the partitions update logic no longer works for other devs. Rework the code to relax the logic and allow updates of MTD parts on devices that are not being used (we still refuse to updates parts on devices who have at least one of their partitions used by someone). Fixes: 5db66b3aee6f ("cmd: mtd: add 'mtd' command") Signed-off-by: Boris Brezillon Tested-by: Heiko Schocher --- drivers/mtd/mtd_uboot.c | 96 +++++++++++++++++++++++++++++------------ drivers/mtd/mtdpart.c | 12 ++++++ include/linux/mtd/mtd.h | 2 + 3 files changed, 82 insertions(+), 28 deletions(-) diff --git a/drivers/mtd/mtd_uboot.c b/drivers/mtd/mtd_uboot.c index 6a36948b917..d638f700d04 100644 --- a/drivers/mtd/mtd_uboot.c +++ b/drivers/mtd/mtd_uboot.c @@ -149,6 +149,54 @@ static const char *get_mtdparts(void) return mtdparts; } +static int mtd_del_parts(struct mtd_info *mtd, bool quiet) +{ + int ret; + + if (!mtd_has_partitions(mtd)) + return 0; + + /* do not delete partitions if they are in use. */ + if (mtd_partitions_used(mtd)) { + if (!quiet) + printf("\"%s\" partitions still in use, can't delete them\n", + mtd->name); + return -EACCES; + } + + ret = del_mtd_partitions(mtd); + if (ret) + return ret; + + return 1; +} + +static bool mtd_del_all_parts_failed; + +static void mtd_del_all_parts(void) +{ + struct mtd_info *mtd; + int ret = 0; + + mtd_del_all_parts_failed = false; + + /* + * It is not safe to remove entries from the mtd_for_each_device loop + * as it uses idr indexes and the partitions removal is done in bulk + * (all partitions of one device at the same time), so break and + * iterate from start each time a new partition is found and deleted. + */ + do { + mtd_for_each_device(mtd) { + ret = mtd_del_parts(mtd, false); + if (ret > 0) + break; + else if (ret < 0) + mtd_del_all_parts_failed = true; + } + } while (ret > 0); +} + int mtd_probe_devices(void) { static char *old_mtdparts; @@ -156,18 +204,19 @@ int mtd_probe_devices(void) const char *mtdparts = get_mtdparts(); const char *mtdids = get_mtdids(); const char *mtdparts_next = mtdparts; - bool remaining_partitions = true; struct mtd_info *mtd; mtd_probe_uclass_mtd_devs(); /* - * Check if mtdparts/mtdids changed or if the MTD dev list was updated - * since last call, otherwise: exit + * Check if mtdparts/mtdids changed, if the MTD dev list was updated + * or if our previous attempt to delete existing partititions failed. + * In any of these cases we want to update the partitions, otherwise, + * everything is up-to-date and we can return 0 directly. */ if ((!mtdparts && !old_mtdparts && !mtdids && !old_mtdids) || (mtdparts && old_mtdparts && mtdids && old_mtdids && - !mtd_dev_list_updated() && + !mtd_dev_list_updated() && !mtd_del_all_parts_failed && !strcmp(mtdparts, old_mtdparts) && !strcmp(mtdids, old_mtdids))) return 0; @@ -178,32 +227,12 @@ int mtd_probe_devices(void) old_mtdparts = strdup(mtdparts); old_mtdids = strdup(mtdids); - /* If at least one partition is still in use, do not delete anything */ - mtd_for_each_device(mtd) { - if (mtd->usecount) { - printf("Partition \"%s\" already in use, aborting\n", - mtd->name); - return -EACCES; - } - } - /* - * Everything looks clear, remove all partitions. It is not safe to - * remove entries from the mtd_for_each_device loop as it uses idr - * indexes and the partitions removal is done in bulk (all partitions of - * one device at the same time), so break and iterate from start each - * time a new partition is found and deleted. + * Remove all old parts. Note that partition removal can fail in case + * one of the partition is still being used by an MTD user, so this + * does not guarantee that all old partitions are gone. */ - while (remaining_partitions) { - remaining_partitions = false; - mtd_for_each_device(mtd) { - if (!mtd_is_partition(mtd) && mtd_has_partitions(mtd)) { - del_mtd_partitions(mtd); - remaining_partitions = true; - break; - } - } - } + mtd_del_all_parts(); /* * Call mtd_dev_list_updated() to clear updates generated by our own @@ -278,6 +307,17 @@ int mtd_probe_devices(void) } } + /* + * Call mtd_del_parts() again, even if it's already been called + * in mtd_del_all_parts(). We need to know if old partitions are + * still around (because they are still being used by someone), + * and if they are, we shouldn't create new partitions, so just + * skip this MTD device and try the next one. + */ + ret = mtd_del_parts(mtd, true); + if (ret < 0) + continue; + /* * Parse the MTD device partitions. It will update the mtdparts * pointer, create an array of parts (that must be freed), and diff --git a/drivers/mtd/mtdpart.c b/drivers/mtd/mtdpart.c index 4d2ac8107f0..fd8d8e5ea72 100644 --- a/drivers/mtd/mtdpart.c +++ b/drivers/mtd/mtdpart.c @@ -63,6 +63,18 @@ char *kstrdup(const char *s, gfp_t gfp) #define MTD_SIZE_REMAINING (~0LLU) #define MTD_OFFSET_NOT_SPECIFIED (~0LLU) +bool mtd_partitions_used(struct mtd_info *master) +{ + struct mtd_info *slave; + + list_for_each_entry(slave, &master->partitions, node) { + if (slave->usecount) + return true; + } + + return false; +} + /** * mtd_parse_partition - Parse @mtdparts partition definition, fill @partition * with it and update the @mtdparts string pointer. diff --git a/include/linux/mtd/mtd.h b/include/linux/mtd/mtd.h index 4d0096d9f1d..cd1f557a2f3 100644 --- a/include/linux/mtd/mtd.h +++ b/include/linux/mtd/mtd.h @@ -366,6 +366,8 @@ static inline bool mtd_has_partitions(const struct mtd_info *mtd) return !list_empty(&mtd->partitions); } +bool mtd_partitions_used(struct mtd_info *master); + int mtd_ooblayout_ecc(struct mtd_info *mtd, int section, struct mtd_oob_region *oobecc); int mtd_ooblayout_find_eccregion(struct mtd_info *mtd, int eccbyte, From 7371944a71690abafd0717b5d5f72c67e9f0f414 Mon Sep 17 00:00:00 2001 From: Boris Brezillon Date: Sun, 2 Dec 2018 10:54:31 +0100 Subject: [PATCH 27/28] mtd: sf: Unregister the MTD device prior to removing the spi_flash obj The DM implementation of spi_flash_free() does not unregister the MTD device before removing the spi dev object. This leads to a use-after-free bug when the MTD device is later accessed by a MTD user (observed when attaching the device to UBI after env_sf_load() has called spi_flash_free()). Implement ->remove() and call spi_flash_mtd_unregister() from there. Fixes: 9fe6d8716e09 ("mtd, spi: Add MTD layer driver") Signed-off-by: Boris Brezillon Tested-by: Heiko Schocher Reviewed-by: Jagan Teki --- drivers/mtd/spi/sf_probe.c | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/drivers/mtd/spi/sf_probe.c b/drivers/mtd/spi/sf_probe.c index 94fde2ae7a3..4d7320fe8c1 100644 --- a/drivers/mtd/spi/sf_probe.c +++ b/drivers/mtd/spi/sf_probe.c @@ -137,6 +137,14 @@ static int spi_flash_std_probe(struct udevice *dev) return spi_flash_probe_slave(flash); } +static int spi_flash_std_remove(struct udevice *dev) +{ +#ifdef CONFIG_SPI_FLASH_MTD + spi_flash_mtd_unregister(); +#endif + return 0; +} + static const struct dm_spi_flash_ops spi_flash_std_ops = { .read = spi_flash_std_read, .write = spi_flash_std_write, @@ -153,6 +161,7 @@ U_BOOT_DRIVER(spi_flash_std) = { .id = UCLASS_SPI_FLASH, .of_match = spi_flash_std_ids, .probe = spi_flash_std_probe, + .remove = spi_flash_std_remove, .priv_auto_alloc_size = sizeof(struct spi_flash), .ops = &spi_flash_std_ops, }; From 08898e8b22d74a4511eadee9b06b11aab43e809c Mon Sep 17 00:00:00 2001 From: Boris Brezillon Date: Sun, 2 Dec 2018 10:54:32 +0100 Subject: [PATCH 28/28] mtd: sf: Make sf_mtd.c more robust SPI flash based MTD devs can be registered/unregistered at any time through the sf probe command or the spi_flash_free() function. This commit does not try to fix the root cause as it would probably require rewriting most of the code and have an mtd_info object instance per spi_flash object (not to mention that the the spi-flash layer is likely to be replaced by a spi-nor layer ported from Linux). Instead, we try to be as safe as can be by checking the code returned by del_mtd_device() and complain loudly when there's nothing we can do about the deregistration failure. When that happens we also reset sf_mtd_info.priv to NULL, and check for NULL pointer in the mtd hooks so that -ENODEV is returned instead of hitting a NULL pointer dereference exception when the MTD instance is later accessed by a user. Signed-off-by: Boris Brezillon Tested-by: Heiko Schocher --- drivers/mtd/spi/sf_mtd.c | 39 ++++++++++++++++++++++++++++++++++++--- 1 file changed, 36 insertions(+), 3 deletions(-) diff --git a/drivers/mtd/spi/sf_mtd.c b/drivers/mtd/spi/sf_mtd.c index aabbc358943..68c36002bee 100644 --- a/drivers/mtd/spi/sf_mtd.c +++ b/drivers/mtd/spi/sf_mtd.c @@ -18,6 +18,9 @@ static int spi_flash_mtd_erase(struct mtd_info *mtd, struct erase_info *instr) struct spi_flash *flash = mtd->priv; int err; + if (!flash) + return -ENODEV; + instr->state = MTD_ERASING; err = spi_flash_erase(flash, instr->addr, instr->len); @@ -39,6 +42,9 @@ static int spi_flash_mtd_read(struct mtd_info *mtd, loff_t from, size_t len, struct spi_flash *flash = mtd->priv; int err; + if (!flash) + return -ENODEV; + err = spi_flash_read(flash, from, len, buf); if (!err) *retlen = len; @@ -52,6 +58,9 @@ static int spi_flash_mtd_write(struct mtd_info *mtd, loff_t to, size_t len, struct spi_flash *flash = mtd->priv; int err; + if (!flash) + return -ENODEV; + err = spi_flash_write(flash, to, len, buf); if (!err) *retlen = len; @@ -76,8 +85,13 @@ int spi_flash_mtd_register(struct spi_flash *flash) { int ret; - if (sf_mtd_registered) - del_mtd_device(&sf_mtd_info); + if (sf_mtd_registered) { + ret = del_mtd_device(&sf_mtd_info); + if (ret) + return ret; + + sf_mtd_registered = false; + } sf_mtd_registered = false; memset(&sf_mtd_info, 0, sizeof(sf_mtd_info)); @@ -110,5 +124,24 @@ int spi_flash_mtd_register(struct spi_flash *flash) void spi_flash_mtd_unregister(void) { - del_mtd_device(&sf_mtd_info); + int ret; + + if (!sf_mtd_registered) + return; + + ret = del_mtd_device(&sf_mtd_info); + if (!ret) { + sf_mtd_registered = false; + return; + } + + /* + * Setting mtd->priv to NULL is the best we can do. Thanks to that, + * the MTD layer can still call mtd hooks without risking a + * use-after-free bug. Still, things should be fixed to prevent the + * spi_flash object from being destroyed when del_mtd_device() fails. + */ + sf_mtd_info.priv = NULL; + printf("Failed to unregister MTD %s and the spi_flash object is going away: you're in deep trouble!", + sf_mtd_info.name); }