1. 程式人生 > >Linux SPI NOR 分析(FSL-QUADSPI)

Linux SPI NOR 分析(FSL-QUADSPI)

下圖是Linux SPI NOR驅動中讀數寫資料的流程,寫的流程和讀類似。具體裝置以i.MX SOC上的QuadSPI為例
這裡寫圖片描述

MTD層

MTDBLOCK

看完上面的文章可以看到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: [email protected]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;
}

相關推薦

Linux SPI NOR 分析(FSL-QUADSPI)

下圖是Linux SPI NOR驅動中讀數寫資料的流程,寫的流程和讀類似。具體裝置以i.MX SOC上的QuadSPI為例 MTD層 MTDBLOCK 看完上面的文章可以看到MTDBLOCK的初始化也是標準的塊裝置驅動程式流程,申請一個gend

Linux spi驅動分析(一)----匯流排驅動

一、SPI匯流排驅動介紹         SPI匯流排總共需要四根線,包括MOSI、MISO、CLK和CS。本文首先從SPI設備註冊開始來講述SPI匯流排驅動。 二、設備註冊         在系統啟動的時候,會按照順序執行一些初始化程式,比如device_initcall和module_i

Linux spi驅動分析(三)----spiddev分析

static longspidev_ioctl(struct file *filp, unsigned int cmd, unsigned long arg){    int            err = 0;    int            retval = 0;    struct spid

Linux spi驅動分析(二)----SPI核心(bus、device_driver和device)

struct device {     struct device        *parent;     struct device_private    *p;     struct kobject kobj;     const char        *init_name; /* init

linuxspi-nor Flash的操作----備份與還原norflash中的uboot

一、環境: Ubuntu 16.02 nor flash型號: spi nor flash S25FL256S ,Sector Size = 64 kbytes,  total size = 256M BIT = 32M Bytes 一、備份: dd if=/dev/mtd

linux spi驅動開發學習(四)-----spi驅動程式完整流程分析

所有的應用程式使用dev/目錄下建立的裝置,這些字元裝置的操作函式集在檔案spidev.c中實現。 點選(此處)摺疊或開啟 static const struct file_operations spidev_fops = {     .owner =    THIS

iostat命令具體解釋——linux性能分析

毫秒 名稱 inux linux性能 多個 nice 是我 技術 art 之前總結uptime和free命令,今天繼續來總結一下iostat。給自己留個筆記。同一時候也希望對大家實用。 版本號信息: sysstat version

linux 性能分析

linux count 平均值 信息 查看性能順序:[cpu] mpstat -P ALL 1 100 (sar -u,sar -p)[network] sar -n DEV[disk] sar -b,sar -d[mem] sar -W,sar -r,sar -BtopLinux CPU實

Linux內核分析 - 網絡[十四]:IP選項

ria copyto 還要 next 操作 目的 start 套接口 詳細講解 Linux內核分析 - 網絡[十四]:IP選項 標簽: linux內核網絡structsocketdst 2012-04-25 17:14 5639人閱讀 評論(1) 收藏 舉報

linux系統故障分析與排查

使用 權限 建立 shel 自動識別 了解 緊急 rhel5 1.4 在處理Linux系統出現的各種故障時,故障的癥狀是最先發現的,而導致這以故障的原因才是最終排除故障的關鍵。熟悉Linux系統的日誌管理,了解常見故障的分析與解決辦法,將有助於管理員快速定位故障點。“對癥下

X86架構下Linux啟動過程分析

重要 ack csdn 檢查 point article span 註意 eap 1、X86架構下的從開機到Start_kernel啟動的整體過程 這個過程簡要概述為: 開機——>BIOS——>GRUB/LILO——>Linux Kernel

Linux——信息分析(四)域名分析dig、host、

p地址 blog alt src org amt png 負責 一級域名 1、域名的命名格式為:WWW.<用戶名>.<二級域名>.<一級域名> dig www.baidu.com 解析過程說明

Linux系統故障分析與排查--日誌分析

獲得 cat cron stl 文本格式 etc 服務的啟動 網絡 調試   處理Linux系統出現的各種故障時,故障的癥狀是最先發現的,而導致這以故障的原因才是最終排除故障的關鍵。熟悉Linux系統的日誌管理,了解常見故障的分析與解決辦法,將有助於管理員快速定位故障點,“

嵌入式開發之hi3519---spi nor flash啟動

flash light thread sta 引導 mod 開發 啟動 emmc author:pkf qq:1327706646 1.官方hi3519默認是硬件3byte 地址模式,配置完ddr始終後,sdkv100.020是可以支持正常啟動,用到低位16Mflash

Linux內核分析+子安全系統selinux+Linux的用戶組和用戶

rm命令 裝載 hidden 其他 今天 ups nfs 根目錄 開機 一.Linux內核分析/etc/grub.conf文件 1.passwd命令 Linux以安全性和穩定性在世界上自居,在Linux發明之初就在安全領域做了很多手段,其中最簡單就是提供了密碼的登錄和密碼修

Linux性能分析Top

uptime命令 共享內存 roc 分區 ima 多次 轉換 知識 hist 前言 在實際開發中,有時候會收到一些服務的監控報警,比如CPU飆高,內存飆高等,這個時候,我們會登錄到服務器上進行排查。本篇博客將涵蓋這方面的知識:Linux性能工具。 一次線上問題排查模擬

linux中日誌分析及系統故障的修復

執行 term ima 查詢 mbr sys 是否 mark .com 步驟:1查看及分析/var/log/messsages日誌文件2查看及分析用戶登錄日誌Last:命令用於查詢成功登錄到系統的用戶記錄Lastb命令用於查詢登錄失敗的用戶記錄MBR扇區故障1.備份MBR扇

Linux性能分析工具

套接字 間隔 數量 linux性能 read 就會 img con 分鐘   影響Linux服務器性能的因素有很多,從底層的硬件到操作系統,從網絡到上層應用。找到系統硬件和軟件資源的平衡點是關鍵。 1.uptime 10:27:14 up 18 min, 5 users,

第一次個人作業【六】(Linux性能分析

nal pprof 適合 right 比例 link 正常 and HA 工具選擇 這裏參考了http://gernotklingler.com/blog/gprof-valgrind-gperftools-evaluation-tools-application-leve

學不好Linux?我們分析看看正確的學習方法是什麽

Linux Linux入門 Linux運維 2018年裏,Linux運維的職位數量和平均薪資水平仍然持續了去年的強勁增幅,比很多開發崗位漲的都快。從研究機構的數據來看,Linux職位數量和工資水平漲幅均在IT行業的前五之列,比去年的表現還要好一點。在這樣的前提下,很多人加入Linux運維的學習行列