下圖是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, ¶ms); // 從註釋上就可以看到這個函數是根據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, ¶ms, 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(¶ms->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(¶ms->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(¶ms->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(¶ms->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(¶ms->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;
}