Linux SPI NOR 分析(FSL-QUADSPI)

下圖是Linux SPI NOR驅動中讀數寫數據的流程,寫的流程和讀類似。具體設備以i.MX SOC上的QuadSPI爲例
這裏寫圖片描述

MTD層

MTDBLOCK

MTDBLOCK層的分析網上有很多了,這裏有一篇MTDBLOCK的初始化過程的源碼分析,講解的挺詳細的,並且代碼也是最新的。
https://www.cnblogs.com/ken-song2016/articles/5627505.html

看完上面的文章可以看到MTDBLOCK的初始化也是標準的塊設備驅動程序流程,申請一個gendisk,設置,之後在註冊,設置請求隊列等。最終MTDBLOCK處理請求隊列的函數是do_blktrans_request

讀操作會在REQ_OP_READ分支中完成,處理每一個request中的bio,調用tr->readsect(dev, block, buf),這個函數調用的是mtdcore的函數,可以在後面看到。
寫操作也類似,會調用到mtdcore的write函數中。

mtd_blkdevs.c

static blk_status_t do_blktrans_request(struct mtd_blktrans_ops *tr,
                   struct mtd_blktrans_dev *dev,
                   struct request *req)
{
    unsigned long block, nsect;
    char *buf;

    block = blk_rq_pos(req) << 9 >> tr->blkshift;//要處理的扇區
    nsect = blk_rq_cur_bytes(req) >> tr->blkshift;//傳送的扇區數目

    if (req_op(req) == REQ_OP_FLUSH) {
        if (tr->flush(dev))
            return BLK_STS_IOERR;
        return BLK_STS_OK;
    }

    if (blk_rq_pos(req) + blk_rq_cur_sectors(req) >
        get_capacity(req->rq_disk)) //檢查是否在容量內
        return BLK_STS_IOERR;

    switch (req_op(req)) {
    case REQ_OP_DISCARD:
        if (tr->discard(dev, block, nsect))
            return BLK_STS_IOERR;
        return BLK_STS_OK;
    case REQ_OP_READ:                           //讀操作
        buf = kmap(bio_page(req->bio)) + bio_offset(req->bio);
        for (; nsect > 0; nsect--, block++, buf += tr->blksize) {
            if (tr->readsect(dev, block, buf)) {
                kunmap(bio_page(req->bio));
                return BLK_STS_IOERR;
            }
        }
        kunmap(bio_page(req->bio));
        rq_flush_dcache_pages(req);
        return BLK_STS_OK;
    case REQ_OP_WRITE:                          //寫操作
        if (!tr->writesect)
            return BLK_STS_IOERR;

        rq_flush_dcache_pages(req);
        buf = kmap(bio_page(req->bio)) + bio_offset(req->bio);
        for (; nsect > 0; nsect--, block++, buf += tr->blksize) {
            if (tr->writesect(dev, block, buf)) {
                kunmap(bio_page(req->bio));
                return BLK_STS_IOERR;
            }
        }
        kunmap(bio_page(req->bio));
        return BLK_STS_OK;
    default:
        return BLK_STS_IOERR;
    }
}

我們來看下MTDBLOCK中mtdblock_tr的定義

mtdblock.c

static struct mtd_blktrans_ops mtdblock_tr = {
    .name       = "mtdblock",
    .major      = MTD_BLOCK_MAJOR,
    .part_bits  = 0,
    .blksize    = 512,
    .open       = mtdblock_open,
    .flush      = mtdblock_flush,
    .release    = mtdblock_release,
    .readsect   = mtdblock_readsect,
    .writesect  = mtdblock_writesect,
    .add_mtd    = mtdblock_add_mtd,
    .remove_dev = mtdblock_remove_dev,
    .owner      = THIS_MODULE,
};

ok,來看一下mtdblock_readsect,也就是上面處理I/O請求的函數。mtdblock_readsect僅僅調用了do_cached_read。do_cached_read會根據要讀的數據是不是已經存在於MTDBLOCK中的緩存buf中,如果命中,那麼直接從MTDBLOCK的緩存buf中讀出數據。如果不存在,那麼就調用mtd_read從下層讀數據上來。這個緩存機制跟文件系統啊,STDIO中的緩存類似。

寫的邏輯也類似,細節上有些不同,最終會調用do_cached_write,會調用mtd_erase和mtd_write

mtdblock.c

static int do_cached_read (struct mtdblk_dev *mtdblk, unsigned long pos,
               int len, char *buf)
{
    struct mtd_info *mtd = mtdblk->mbd.mtd;
    unsigned int sect_size = mtdblk->cache_size;
    size_t retlen;
    int ret;

    pr_debug("mtdblock: read on \"%s\" at 0x%lx, size 0x%x\n",
            mtd->name, pos, len);

    if (!sect_size)
        return mtd_read(mtd, pos, len, &retlen, buf);

    while (len > 0) {
        unsigned long sect_start = (pos/sect_size)*sect_size;
        unsigned int offset = pos - sect_start;
        unsigned int size = sect_size - offset;
        if (size > len)
            size = len;

        /*
         * Check if the requested data is already cached
         * Read the requested amount of data from our internal cache if it
         * contains what we want, otherwise we read the data directly
         * from flash.
         */
        if (mtdblk->cache_state != STATE_EMPTY &&
            mtdblk->cache_offset == sect_start) {
            memcpy (buf, mtdblk->cache_data + offset, size);
        } else {
            ret = mtd_read(mtd, pos, size, &retlen, buf);
            if (ret)
                return ret;
            if (retlen != size)
                return -EIO;
        }

        buf += size;
        pos += size;
        len -= size;
    }

    return 0;
}

static int mtdblock_readsect(struct mtd_blktrans_dev *dev,
                  unsigned long block, char *buf)
{
    struct mtdblk_dev *mtdblk = container_of(dev, struct mtdblk_dev, mbd);
    return do_cached_read(mtdblk, block<<9, 512, buf);
}

MTDCORE

上面提到MTDBLOCK中的mtdblock_readsect最終調用到了MTDCORE中的mtd_read函數。m如果mtd_info中的_read函數存在,那麼就調用_read函數,否則調用_read_oob。我們這裏只看這個_read函數,那麼個這個read函數是從哪來的呢?它是從不同的設備驅動傳上來的。本文分析是SPI-NOR,那麼該_read函數就是由SPI NOR層註冊上來的,後來我們來看一下SPI NOR層是如何初始化的。

mtdcore.c

int mtd_read(struct mtd_info *mtd, loff_t from, size_t len, size_t *retlen,
         u_char *buf)
{
    int ret_code;
    *retlen = 0;
    ...
    /*
     * In the absence of an error, drivers return a non-negative integer
     * representing the maximum number of bitflips that were corrected on
     * any one ecc region (if applicable; zero otherwise).
     */
    if (mtd->_read) {
        ret_code = mtd->_read(mtd, from, len, retlen, buf);     //\_read是由SPI-NOR層註冊的
    } else if (mtd->_read_oob) {
        struct mtd_oob_ops ops = {
            .len = len,
            .datbuf = buf,
        };

        ret_code = mtd->_read_oob(mtd, from, &ops);
        *retlen = ops.retlen;
    } else {
        return -ENOTSUPP;
    }
    ...
}

SPI NOR層

上面很潦草地分析了下MTD層的一些函數,主要是爲了說明SPI NOR也是一種塊設備。它的初始化流程其實也是一種標準的塊設備,只是內核幫我們封裝了一層MTDBLOCK,這樣一些塊設備的通用操作,比如gendisk的分配/註冊,請求隊列,bio的處理全部都由內核封裝好了,設備驅動主需要專注於硬件的操作就可以了。

SPI NOR層代碼:driver/mtd/spi-nor/spi-nor.c

spi_nor結構

spi_nor層主要的就是這個結構體了。
- 首先它包含了一個mtd_info的對象mtd,這個對象連接SPI NOR層和MTDCORE層,SPI NOR會在初始化的時候註冊這個mtd,這樣上層MTDCORE就能調用到SPI NOR的讀寫函數了。
- info是SPI NOR中自己定義的一個結構體,主要描述了這個SPI NOR的信息,比如page的大小等等。
- read_proto, write_proto, reg_proto針對不同的SPI NOR FLASH而定義的。不同的SPI NOR FLASH有不一樣的讀寫命令,比如Quad SPI FLASH和Octal SPI Flash的讀寫命令是不同的, 包括不同廠家的同一種類型的SPI NOR FLASH讀寫命令都是不一樣的,比如MICRON的MT35X和MXIC的MX25系列的Octol FLASH讀寫是完全不一樣的。
- 接下來就是定義了一些SPI NOR的操作接口,prepare, unprepare, read_reg, write_reg, read, write, erase, flash_lock, flash_unlock, flash_is_locked, quad_enable。後面可以看到SPI NOR的下層模塊不一定會實現這些接口的全部。SPI NOR也沒有強制說要實現所有的接口。
- priv用於指向下層對象的指針,比如本文例子中就是struct fsl_qspi,在下一節會提到。

struct spi_nor {
    struct mtd_info     mtd;
    struct mutex        lock;
    struct device       *dev;
    const struct flash *info;
    u32         page_size;
    u8          addr_width;
    u8          erase_opcode;
    u8          read_opcode;
    u8          read_dummy;
    u8          program_opcode;
    enum spi_nor_protocol   read_proto;
    enum spi_nor_protocol   write_proto;
    enum spi_nor_protocol   reg_proto;
    bool            sst_write_second;
    u32         flags;
    u8          cmd_buf[SPI_NOR_MAX_CMD_SIZE];

    int (*prepare)(struct spi_nor *nor, enum spi_nor_ops ops);
    void (*unprepare)(struct spi_nor *nor, enum spi_nor_ops ops);
    int (*read_reg)(struct spi_nor *nor, u8 opcode, u8 *buf, int len);
    int (*write_reg)(struct spi_nor *nor, u8 opcode, u8 *buf, int len);

    ssize_t (*read)(struct spi_nor *nor, loff_t from,
            size_t len, u_char *read_buf);
    ssize_t (*write)(struct spi_nor *nor, loff_t to,
            size_t len, const u_char *write_buf);
    int (*erase)(struct spi_nor *nor, loff_t offs);

    int (*flash_lock)(struct spi_nor *nor, loff_t ofs, uint64_t len);
    int (*flash_unlock)(struct spi_nor *nor, loff_t ofs, uint64_t len);
    int (*flash_is_locked)(struct spi_nor *nor, loff_t ofs, uint64_t len);
    int (*quad_enable)(struct spi_nor *nor);

    void *priv;
};

對外接口

SPI NOR對外只暴露了這兩個接口,最主要的就是spi_nor_scan,這個接口承上啓下。下層驅動需要在初始化函數調用改接口,向MTDCORE層註冊mtd info,初始化spi_nor結構體,同時對某些特定的SPI NOR FLASH進行初始化。

spi_nor_restore這個函數我也不知道怎麼用,在fsl_quadspi.c中,也就是SPI NOR下層的具體驅動沒有調用到這個函數。

/**
 * spi_nor_scan() - scan the SPI NOR
 * @nor:    the spi_nor structure
 * @name:   the chip type name
 * @hwcaps: the hardware capabilities supported by the controller driver
 *
 * The drivers can use this fuction to scan the SPI NOR.
 * In the scanning, it will try to get all the necessary information to
 * fill the mtd_info{} and the spi_nor{}.
 *
 * The chip type name can be provided through the @name parameter.
 *
 * Return: 0 for success, others for failure.
 */
int spi_nor_scan(struct spi_nor *nor, const char *name,
         const struct spi_nor_hwcaps *hwcaps);

/**
 * spi_nor_restore_addr_mode() - restore the status of SPI NOR
 * @nor:    the spi_nor structure
 */
void spi_nor_restore(struct spi_nor *nor);

spi_nor_scan

直接來看一下這個函數幹了什麼,這個函數比較長,就貼出主幹代碼,將一些錯誤判斷就略去了。代碼的分析直接就嵌在代碼中。Note:c語言的註釋coding style要用/**/而不是//,這裏方便就用的//

int spi_nor_scan(struct spi_nor *nor, const char *name,
         const struct spi_nor_hwcaps *hwcaps)
{
    struct spi_nor_flash_parameter params;
    const struct flash_info *info = NULL;
    struct device *dev = nor->dev;
    struct mtd_info *mtd = &nor->mtd;
    struct device_node *np = spi_nor_get_flash_node(nor);
    int ret;
    int i;

    /* Reset SPI protocol for all commands. */
    //這裏讀者應該瞭解SPI NOR FLASH的特點,1-1-1代表cmd,addr和data都是一根線傳輸
    //類似的,1-4-4表示cmd爲1線傳輸,addr是4線傳輸,data是四線傳輸,一般QSPI NOR FLASH就是這樣的模式
    nor->reg_proto = SNOR_PROTO_1_1_1;          //初始化寄存器操作時序爲1-1-1
    nor->read_proto = SNOR_PROTO_1_1_1;         //初始化讀時序爲1-1-1
    nor->write_proto = SNOR_PROTO_1_1_1;        //初始化寫時序爲1-1-1

    if (!info)
        info = spi_nor_read_id(nor);            //從SPI NOR FLASH中讀出MANU ID,然後根據ID 填充struct flash_info結構體
                                                //這個結構也是用於描述SPI NOR FLASH的信息,spi_nor_read_id這個函數可以後面看
     ...
   /* Parse the Serial Flash Discoverable Parameters table. */
    ret = spi_nor_init_params(nor, info, &params);       // 從註釋上就可以看到這個函數是根據FLASH中SFDP填充nor和params的一些字段,這個函數後面看

    //下面這一串代碼就是建立SPI NOR層和MTDCORE的聯繫,簡而言之,就是將spi_nor中mtd對象相應字段填充
    if (!mtd->name)
        mtd->name = dev_name(dev);
       mtd->priv = nor;
    mtd->type = MTD_NORFLASH;
    mtd->writesize = 1;
    mtd->flags = MTD_CAP_NORFLASH;
    mtd->size = params.size;
    mtd->_erase = spi_nor_erase;
    mtd->_read = spi_nor_read;
    mtd->_resume = spi_nor_resume;
    //照理說在這之後應該要調用mtd_device_register,可是在這個函數裏面並沒有調用,所以需要在下層驅動中顯示調用mtd_device_register
    //下面函數設置flash_lock,flash_unlock和flash_is_locked。需要注意的是並不是所有的SPI NOR FLASH需要這幾個接口
    //可以看到一開始是判斷是否ST或者Micron的FLASH,如果是的話,設置這三個接口爲默認的stm_xxx
    /* NOR protection support for STmicro/Micron chips and similar */
    if (JEDEC_MFR(info) == SNOR_MFR_MICRON ||
            info->flags & SPI_NOR_HAS_LOCK) {
        nor->flash_lock = stm_lock;
        nor->flash_unlock = stm_unlock;
        nor->flash_is_locked = stm_is_locked;
    }
    //如果下層驅動又實現了這三個接口,那麼就使用下層驅動實現的接口
    if (nor->flash_lock && nor->flash_unlock && nor->flash_is_locked) {
        mtd->_lock = spi_nor_lock;
        mtd->_unlock = spi_nor_unlock;
        mtd->_is_locked = spi_nor_is_locked;
    }
    //實現write接口,這裏特例是SST的FLASH不一樣,其他都使用下層驅動實現的接口。不太理解SST是什麼。
    /* sst nor chips use AAI word program */
    if (info->flags & SST_WRITE)
        mtd->_write = sst_write;
    else
        mtd->_write = spi_nor_write;
    //根據SFDP裏面的信息去設置一些flags,這主要不同的SPI NOR FLASH有不一樣的特性,這裏就不展開了。
    if (info->flags & USE_FSR)
        nor->flags |= SNOR_F_USE_FSR;
    if (info->flags & SPI_NOR_HAS_TB)
        nor->flags |= SNOR_F_HAS_SR_TB;
    if (info->flags & NO_CHIP_ERASE)
        nor->flags |= SNOR_F_NO_OP_CHIP_ERASE;
    if (info->flags & USE_CLSR)
        nor->flags |= SNOR_F_USE_CLSR;
    //設置spi_nor中mtd的一些字段,
    mtd->dev.parent = dev;
    nor->page_size = params.page_size;
    mtd->writebufsize = nor->page_size;

    ...

    /*
     * Configure the SPI memory:
     * - select op codes for (Fast) Read, Page Program and Sector Erase.
     * - set the number of dummy cycles (mode cycles + wait states).
     * - set the SPI protocols for register and memory accesses.
     * - set the Quad Enable bit if needed (required by SPI x-y-4 protos).
     */
     //註釋寫的很明白了,選擇合適的讀寫時序,然後有必要的需要設置quad enable的bit,這個只針對部分QSPI FLASH。
     ret = spi_nor_setup(nor, info, &params, hwcaps);

    ...
     /* Send all the required SPI flash commands to initialize device */
    nor->info = info;
    //這一步會初始化SPI NOR FLASH硬件,不是SPI Host控制器。
    ret = spi_nor_init(nor);
    return 0;
}       

spi_nor_read_id

這個函數實現很簡單, 從SPI NOR FLASH中讀出MANU ID, 然後根據ID 去查找相應的flash_info。

static const struct flash_info *spi_nor_read_id(struct spi_nor *nor)
{
    int         tmp;
    u8          id[SPI_NOR_MAX_ID_LEN];
    const struct flash_info *info;

    tmp = nor->read_reg(nor, SPINOR_OP_RDID, id, SPI_NOR_MAX_ID_LEN);
    if (tmp < 0) {
        dev_dbg(nor->dev, "error %d reading JEDEC ID\n", tmp);
        return ERR_PTR(tmp);
    }

    for (tmp = 0; tmp < ARRAY_SIZE(spi_nor_ids) - 1; tmp++) {
        info = &spi_nor_ids[tmp];
        if (info->id_len) {
            if (!memcmp(info->id, id, info->id_len))
                return &spi_nor_ids[tmp];
        }
    }
    dev_err(nor->dev, "unrecognized JEDEC id bytes: %02x, %02x, %02x\n",
        id[0], id[1], id[2]);
    return ERR_PTR(-ENODEV);
}

spi_nor_ids是一個常量數組,它記錄了大部分的SPI NOR FLASH。

static const struct flash_info spi_nor_ids[] = {
    /* Atmel -- some are (confusingly) marketed as "DataFlash" */
    { "at25fs010",  INFO(0x1f6601, 0, 32 * 1024,   4, SECT_4K) },
    { "at25fs040",  INFO(0x1f6604, 0, 64 * 1024,   8, SECT_4K) },

    { "at25df041a", INFO(0x1f4401, 0, 64 * 1024,   8, SECT_4K) },
    { "at25df321",  INFO(0x1f4700, 0, 64 * 1024,  64, SECT_4K) },
    { "at25df321a", INFO(0x1f4701, 0, 64 * 1024,  64, SECT_4K) },
    { "at25df641",  INFO(0x1f4800, 0, 64 * 1024, 128, SECT_4K) },
    ......
}

spi_nor_init_params

這個函數根據之前得到flash_info來初始化spi_nor和spi_nor_flash_parameter中的字段。最後會從SPI NOR FLASH 上讀出SFDP,然後重新設置spi_nor的參數。

這個是有歷史原因的,最早JEDEC沒有發佈SFDP的標準,所以各個廠商的實現又不一樣,所以一個很大的表,後來JEDEC出了SFDP的標準統一了QSPI FLASH的一些信息。但是到現在對於OCTOL FLASH, Hyperflash等比較新的FLASH, JEDEC還有制定標準。

這個函數的註釋非常多,就不逐行解析了。主要還是用來統一不同廠家不同類型的SPI NOR FLASH的讀寫時序。

static int spi_nor_init_params(struct spi_nor *nor,
                   const struct flash_info *info,
                   struct spi_nor_flash_parameter *params)
{
    /* Set legacy flash parameters as default. */
    memset(params, 0, sizeof(*params));

    /* Set SPI NOR sizes. */
    params->size = info->sector_size * info->n_sectors;
    params->page_size = info->page_size;

    /* (Fast) Read settings. */
    params->hwcaps.mask |= SNOR_HWCAPS_READ;
    spi_nor_set_read_settings(&params->reads[SNOR_CMD_READ],
                  0, 0, SPINOR_OP_READ,
                  SNOR_PROTO_1_1_1);

    if (!(info->flags & SPI_NOR_NO_FR)) {
        params->hwcaps.mask |= SNOR_HWCAPS_READ_FAST;
        spi_nor_set_read_settings(&params->reads[SNOR_CMD_READ_FAST],
                      0, 8, SPINOR_OP_READ_FAST,
                      SNOR_PROTO_1_1_1);
    }
    if (info->flags & SPI_NOR_DUAL_READ) {
        params->hwcaps.mask |= SNOR_HWCAPS_READ_1_1_2;
        spi_nor_set_read_settings(&params->reads[SNOR_CMD_READ_1_1_2],
                      0, 8, SPINOR_OP_READ_1_1_2,
                      SNOR_PROTO_1_1_2);
    }

    if (info->flags & SPI_NOR_QUAD_READ) {
        params->hwcaps.mask |= SNOR_HWCAPS_READ_1_1_4;
        spi_nor_set_read_settings(&params->reads[SNOR_CMD_READ_1_1_4],
                      0, 8, SPINOR_OP_READ_1_1_4,
                      SNOR_PROTO_1_1_4);
    }

    /* Page Program settings. */
    params->hwcaps.mask |= SNOR_HWCAPS_PP;
    spi_nor_set_pp_settings(&params->page_programs[SNOR_CMD_PP],
                SPINOR_OP_PP, SNOR_PROTO_1_1_1);

    /* Select the procedure to set the Quad Enable bit. */
    if (params->hwcaps.mask & (SNOR_HWCAPS_READ_QUAD |
                   SNOR_HWCAPS_PP_QUAD)) {
        switch (JEDEC_MFR(info)) {
        case SNOR_MFR_MACRONIX:
            params->quad_enable = macronix_quad_enable;
            break;

        case SNOR_MFR_MICRON:
            break;

        default:
            /* Kept only for backward compatibility purpose. */
            params->quad_enable = spansion_quad_enable;
            break;
        }

        /*
         * Some manufacturer like GigaDevice may use different
         * bit to set QE on different memories, so the MFR can't
         * indicate the quad_enable method for this case, we need
         * set it in flash info list.
         */
        if (info->quad_enable)
            params->quad_enable = info->quad_enable;
    }

    /* Override the parameters with data read from SFDP tables. */
    nor->addr_width = 0;
    nor->mtd.erasesize = 0;
    if ((info->flags & (SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ)) &&
        !(info->flags & SPI_NOR_SKIP_SFDP)) {
        struct spi_nor_flash_parameter sfdp_params;

        memcpy(&sfdp_params, params, sizeof(sfdp_params));
        if (spi_nor_parse_sfdp(nor, &sfdp_params)) {
            nor->addr_width = 0;
            nor->mtd.erasesize = 0;
        } else {
            memcpy(params, &sfdp_params, sizeof(*params));
        }
    }

    return 0;

spi_nor_init

這個函數主要初始化SPI NOR FLASH戲弄
- 對於ATMEL, INTEL,SST的SPI NOR FLASH,需要先寫使能,然後寫status register。
- 對於需要使能quad enable的SPI NOR FLASH, 調用quad_enable函數來使能QSPI FLASH的Quad模式。

static int spi_nor_init(struct spi_nor *nor)
{
    int err;

    /*
     * Atmel, SST, Intel/Numonyx, and others serial NOR tend to power up
     * with the software protection bits set
     */
    if (JEDEC_MFR(nor->info) == SNOR_MFR_ATMEL ||
        JEDEC_MFR(nor->info) == SNOR_MFR_INTEL ||
        JEDEC_MFR(nor->info) == SNOR_MFR_SST ||
        nor->info->flags & SPI_NOR_HAS_LOCK) {
        write_enable(nor);
        write_sr(nor, 0);
        spi_nor_wait_till_ready(nor);
    }

    if (nor->quad_enable) {
        err = nor->quad_enable(nor);
        if (err) {
            dev_err(nor->dev, "quad mode not supported\n");
            return err;
        }
    }

    if ((nor->addr_width == 4) &&
        (JEDEC_MFR(nor->info) != SNOR_MFR_SPANSION) &&
        !(nor->info->flags & SPI_NOR_4B_OPCODES))
        set_4byte(nor, nor->info, 1);

    return 0;
}

到這裏,SPI NOR層的初始化大致流程就完結了。總結一下就是初始化spi_nor結構體中的相關字段,設置spi_nor中mtd_info對象,以及掛接下層驅動傳遞上來的接口實現。在初始化完成之後,可以看一看讀函數的實現。在初始化函數中可以看到mtd->_read = spi_nor_read;而在MTDCORE中的read函數最終調用的就是mtd->_read,這裏對應於spi_nor_read。

在這個函數中最終調用的是nor->read, 在下一節可以看到,nor->read會在SPI NOR的下層驅動中註冊。

static int spi_nor_read(struct mtd_info *mtd, loff_t from, size_t len,
            size_t *retlen, u_char *buf)
{
    struct spi_nor *nor = mtd_to_spi_nor(mtd);
    int ret;

    dev_dbg(nor->dev, "from 0x%08x, len %zd\n", (u32)from, len);

    ret = spi_nor_lock_and_prep(nor, SPI_NOR_OPS_READ);
    if (ret)
        return ret;

    while (len) {
        loff_t addr = from;

        if (nor->flags & SNOR_F_S3AN_ADDR_DEFAULT)
            addr = spi_nor_s3an_addr_convert(nor, addr);

        ret = nor->read(nor, addr, len, buf);
        if (ret == 0) {
            /* We shouldn't see 0-length reads */
            ret = -EIO;
            goto read_err;
        }
        if (ret < 0)
            goto read_err;

        WARN_ON(ret > len);
        *retlen += ret;
        buf += ret;
        from += ret;
        len -= ret;
    }
    ret = 0;

read_err:
    spi_nor_unlock_and_unprep(nor, SPI_NOR_OPS_READ);
    return ret;
}

FSL_QUADSPI層

在SPI NOR下面就是具體的廠商的設備,這裏以FSL的QUADSPI這個IP爲例,來說明具體的驅動實現,對於驅動工程師來說,需要實現也就是這一層,對於上層SPI NOR和MTD CORE,內核都已經幫我們封裝好了。驅動工程師只需要關心SOC上的這個IP怎麼操作就行了。

platform_driver結構定義

static struct platform_driver fsl_qspi_driver = {
    .driver = {
        .name   = "fsl-quadspi",
        .of_match_table = fsl_qspi_dt_ids,
    },
    .probe          = fsl_qspi_probe,
    .remove     = fsl_qspi_remove,
    .suspend    = fsl_qspi_suspend,
    .resume     = fsl_qspi_resume,
};
module_platform_driver(fsl_qspi_driver);

fsl_qspi_probe

直接看probe函數

static int fsl_qspi_probe(struct platform_device *pdev)
{
    const struct spi_nor_hwcaps hwcaps = {
        .mask = SNOR_HWCAPS_READ_1_1_4 |
            SNOR_HWCAPS_PP,
    };
    struct device_node *np = pdev->dev.of_node;
    struct device *dev = &pdev->dev;
    struct fsl_qspi *q;
    struct resource *res;
    struct spi_nor *nor;
    struct mtd_info *mtd;
    int ret, i = 0
#if 0
    qspi1: qspi@21e0000 {
                #address-cells = <1>;
                #size-cells = <0>;
                compatible = "fsl,imx6sx-qspi";
                reg = <0x021e0000 0x4000>, <0x60000000 0x10000000>;
                reg-names = "QuadSPI", "QuadSPI-memory";
                interrupts = <GIC_SPI 107 IRQ_TYPE_LEVEL_HIGH>;
                clocks = <&clks IMX6SX_CLK_QSPI1>,
                     <&clks IMX6SX_CLK_QSPI1>;
                clock-names = "qspi_en", "qspi";
                status = "disabled";
            };

#endif
    q = devm_kzalloc(dev, sizeof(*q), GFP_KERNEL);
    q->nor_num = of_get_child_count(dev->of_node);  //獲取板子上有多少片SPI NOR FLASH,這裏假定只有一片
    q->dev = dev;
    q->devtype_data = of_device_get_match_data(dev);
    platform_set_drvdata(pdev, q);                  //設置platform driver
    res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "QuadSPI");
    q->iobase = devm_ioremap_resource(dev, res);    //獲取QSPI的物理地址,將其映射到內核虛擬地址上。
    q->big_endian = of_property_read_bool(np, "big-endian");
    res = platform_get_resource_byname(pdev, IORESOURCE_MEM,
                    "QuadSPI-memory");              //獲取QSPI的AHB memory物理地址,QSPI的讀操作不是用IP總線,用的是AHB總線
    devm_request_mem_region(dev, res->start, resource_size(res), res->name);    
    q->memmap_phy = res->start;                     //設置qspi AHB memory的物理地址
    q->clk_en = devm_clk_get(dev, "qspi_en");
    q->clk = devm_clk_get(dev, "qspi");
    ret = fsl_qspi_clk_prep_enable(q);              //設置qspi的時鐘
    ret = platform_get_irq(pdev, 0);                //獲取QSPI的中斷號
    ret = devm_request_irq(dev, ret,
            fsl_qspi_irq_handler, 0, pdev->name, q);//註冊中斷,中斷處理程序是fsl_qspi_irq_handler
   ret = fsl_qspi_nor_setup(q);                     //這個函數是初始化SOC上的QuadSPI控制器
   for_each_available_child_of_node(dev->of_node, np) {
        nor = &q->nor[i];
        mtd = &nor->mtd;
        nor->dev = dev;
        spi_nor_set_flash_node(nor, np);
        nor->priv = q;
        //這裏將QSPI的讀寫函數註冊到SPI NOR層
        /* fill the hooks */
        nor->read_reg = fsl_qspi_read_reg;
        nor->write_reg = fsl_qspi_write_reg;
        nor->read = fsl_qspi_read;
        nor->write = fsl_qspi_write;
        nor->erase = fsl_qspi_erase;
        /* set the chip address for READID */
        fsl_qspi_set_base_addr(q, nor);
        //調用SPI NOR層的spi_nor_scan接口,初始化spi_nor結構和mtd_info結構
        ret = spi_nor_scan(nor, NULL, &hwcaps);
        //註冊MTDBLOCK設備
        ret = mtd_device_register(mtd, NULL, 0);
   }
   ret = fsl_qspi_nor_setup_last(q);
   fsl_qspi_clk_disable_unprep(q);
   return 0;
}

至此,QSPI的驅動就初始化完畢。
- 根據DTS初始化QuadSPI控制器
- 設置spi_nor結構
- 註冊mtd_info,註冊mtdblock設備。

fsl_qspi_read

最終從MTDBLOCK的read函數調用到的就是fsl_qspi_read,這個函數就是操作具體的硬件了。i.MX SOC上QuadSPI的讀數據可以通過AHB BUS,而不是通過寄存器的方式,所以看到這個函數裏面並沒有配置寄存器或者DMA之類的,QuadSPI IP已經幫你隱藏了DMA的設置,軟件能直接從QuadSPI AHB Memory空間上使用memcpy將數據讀出來。當然在讀之前,需要ioremap一下,將物理地址轉換成虛擬地址。

static ssize_t fsl_qspi_read(struct spi_nor *nor, loff_t from,
                 size_t len, u_char *buf)
{
    struct fsl_qspi *q = nor->priv;
    u8 cmd = nor->read_opcode;

    /* if necessary,ioremap buffer before AHB read, */
    if (!q->ahb_addr) {
        q->memmap_offs = q->chip_base_addr + from;
        q->memmap_len = len > QUADSPI_MIN_IOMAP ? len : QUADSPI_MIN_IOMAP;

        q->ahb_addr = ioremap_nocache(
                q->memmap_phy + q->memmap_offs,
                q->memmap_len);
        if (!q->ahb_addr) {
            dev_err(q->dev, "ioremap failed\n");
            return -ENOMEM;
        }
    /* ioremap if the data requested is out of range */
    } else if (q->chip_base_addr + from < q->memmap_offs
            || q->chip_base_addr + from + len >
            q->memmap_offs + q->memmap_len) {
        iounmap(q->ahb_addr);

        q->memmap_offs = q->chip_base_addr + from;
        q->memmap_len = len > QUADSPI_MIN_IOMAP ? len : QUADSPI_MIN_IOMAP;
        q->ahb_addr = ioremap_nocache(
                q->memmap_phy + q->memmap_offs,
                q->memmap_len);
        if (!q->ahb_addr) {
            dev_err(q->dev, "ioremap failed\n");
            return -ENOMEM;
        }
    }

    /* Read out the data directly from the AHB buffer.*/
    memcpy(buf, q->ahb_addr + q->chip_base_addr + from - q->memmap_offs,
        len);

    return len;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章