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, ¶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: [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
linux下spi-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運維的學習行列