目录
3.2 初始化控制器(cqspi_controller_init)
3.3.3 spi_nor_scan注册struct spi_nor结构体
3.3.4 mtd device注册(mtd_device_register)
4.1 cqspi_read_reg(read id/status)
4.2 cqspi_write_reg(write status/configuration)
1. 芯片简介
我们分析了spi-nor子系统框架的由来,以及spi-nor子系统的核心函数,下面我们分析一下控制器的实现。从前面一章文章,我们知道使用spi-nor子系统有两种情况。一个是通过SPI总线访问的NOR FLASH,一个是通过QSPI控制器访问的NOR FLASH,如下所示。本文主要介绍的是使用通过QSPI控制器访问的NOR FLASH。
1.1 模块与接口
APB接口用于寄存器访问。
AHB接口用于数据传输。
SPI Flash接口对接QSPI/OSPI Flash芯片。
1.2 访问模式
对于Flash的寄存器访问,仅仅使用APB接口配置控制器寄存器,Flash的寄存器内容也通过控制器寄存器读取或写入;
对于数据访问,通过APB配置控制器寄存器,设置必要的总线访问属性,然后通过AHB访问获取或写入数据内容。
Cadence OSPI有DAC、INDAC两个数据访问控制器,并行工作(而非互斥)。DAC的使能由CQSPI_REG_CONFIG(00h)寄存器的bit7控制,是否使能DAC,不影响INDAC的正常工作。
DAC (Direct Access Controller)模式
AHB访问(AHB基地址开始的一段地址空间)直接触发Flash的读写操作,可用于XIP(片上执行),读写长度不由软件配置控制。
INDAC (Indirect Access Controller)模式
目的是读取确定长度的内容,INDAC模式需要额外配置寄存器设置读写的Flash偏移地址和长度。INDAC访问时数据由SRAM中转,Master通过AHB访问(仅需AHB基地址)读写SRAM中的数据。
采用哪种模式由AHB访问的地址范围决定:
(1)INDAC地址区域内的AHB访问 ---> INDAC模式(使用SRAM)
(2)INDAC地址区域外的AHB访问 ---> DAC模式(不使用SRAM)
INDAC的地址空间范围由寄存器由以下两个寄存器决定:
CQSPI_REG_INDIRECTTRIGGER(1Ch) 配置INDAC访问的起始地址,注意这里必须填OSPI控制器的AHB物理地址 0xe0000000,而不是相对偏移地址0x0。
CQSPI_REG_INDIRECTRANGE(80h) 配置INDAC访问的地址范围,寄存器默认值为4。
INDAC地址区间计算方式: 基地址 - (基地址 + SRAM_DATA_SIZE * (2 ^ range) - 1)
这里SRAM_DATA_SIZE固定为4Byte,所以当前对应的地址区间就是 0xe0000000 - 0xe000003f (对应64 Byte的地址空间)。
2. DTS
qspi: spi@f0d9e000 {
compatible = "cdns,qspi-nor";
#address-cells = <2>;
#size-cells = <2>;
reg = <0x0 0xf0d9e000 0x0 0x1000>,
<0x0 0xe0000000 0x0 0x10000000>;
interrupt-parent = <&gic>;
interrupts = <0 46 4>;
clocks = <&misc_clk>;
cdns,is-decoded-cs;
cdns,fifo-depth = <256>;
cdns,fifo-width = <4>;
cdns,trigger-address = <0xe0000000>;
flash0: giga0@0 {
reg = <0 0 0 0>;
cdns,read-delay = <4>;
cdns,tshsl-ns = <40>;
cdns,tsd2d-ns = <50>;
cdns,tchsh-ns = <3>;
cdns,tslch-ns = <4>;
spi-max-frequency = <1000000>;
#address-cells = <1>;
#size-cells = <1>;
partition@qspi-test0{
label = "test0";
reg = <0x0 0x800000>; /* 8MB */
};
partition@qspi-test1{
label = "test1";
reg = <0x800000 0x800000>; /* 8MB */
};
};
};
上面dts有几个需要主要的地方。
2.1 reg
reg = <0x0 0xf0d9e000 0x0 0x1000>,
<0x0 0xe0000000 0x0 0x10000000>;
第1组mem信息是qspi控制器的信息,第2组mem信息是ahb总线上的mem信息。
2.2 cdns,trigger-address
cdns,trigger-address = <0xe0000000>;
cdns,trigger-address设置的是QSPI控制器INDAC读的时候的触发地址。
2.3 匹配DTS的驱动程序入口
static const struct of_device_id cqspi_dt_ids[] = {
{
.compatible = "cdns,qspi-nor",
.data = (void *)0,
},
{
.compatible = "ti,k2g-qspi",
.data = (void *)CQSPI_NEEDS_WR_DELAY,
},
{ /* end of table */ }
};
MODULE_DEVICE_TABLE(of, cqspi_dt_ids);
static struct platform_driver cqspi_platform_driver = {
.probe = cqspi_probe,
.remove = cqspi_remove,
.driver = {
.name = CQSPI_NAME,
.pm = CQSPI_DEV_PM_OPS,
.of_match_table = cqspi_dt_ids,
},
};
最终cqspi_probe函数被调用。
3. cqspi_probe
3.1 从第1节DTS中得到配置信息、clk等信息
static int cqspi_probe(struct platform_device *pdev)
{
......
/* Obtain configuration from OF. */
ret = cqspi_of_get_pdata(pdev);
if (ret) {
dev_err(dev, "Cannot get mandatory OF data.\n");
return -ENODEV;
}
/* Obtain QSPI clock. */
cqspi->clk = devm_clk_get(dev, NULL);
if (IS_ERR(cqspi->clk)) {
dev_err(dev, "Cannot claim QSPI clock.\n");
return PTR_ERR(cqspi->clk);
}
/* Obtain and remap controller address. */
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
cqspi->iobase = devm_ioremap_resource(dev, res);
if (IS_ERR(cqspi->iobase)) {
dev_err(dev, "Cannot remap controller address.\n");
return PTR_ERR(cqspi->iobase);
}
/* Obtain and remap AHB address. */
res_ahb = platform_get_resource(pdev, IORESOURCE_MEM, 1);
cqspi->ahb_base = devm_ioremap_resource(dev, res_ahb);
if (IS_ERR(cqspi->ahb_base)) {
dev_err(dev, "Cannot remap AHB address.\n");
return PTR_ERR(cqspi->ahb_base);
}
init_completion(&cqspi->transfer_complete);
/* Obtain IRQ line. */
irq = platform_get_irq(pdev, 0);
if (irq < 0) {
dev_err(dev, "Cannot obtain IRQ.\n");
return -ENXIO;
}
ret = clk_prepare_enable(cqspi->clk);
if (ret) {
dev_err(dev, "Cannot enable QSPI clock.\n");
return ret;
}
cqspi->master_ref_clk_hz = clk_get_rate(cqspi->clk);
data = (unsigned long)of_device_get_match_data(dev);
if (data & CQSPI_NEEDS_WR_DELAY)
cqspi->wr_delay = 5 * DIV_ROUND_UP(NSEC_PER_SEC,
cqspi->master_ref_clk_hz);
ret = devm_request_irq(dev, irq, cqspi_irq_handler, 0,
pdev->name, cqspi);
if (ret) {
dev_err(dev, "Cannot request IRQ.\n");
goto probe_irq_failed;
}
........
}
从第1节DTS中得到配置信息、clk等信息、mem信息等等。
3.2 初始化控制器(cqspi_controller_init)
static int cqspi_probe(struct platform_device *pdev)
{
......
cqspi_wait_idle(cqspi);
cqspi_controller_init(cqspi);
cqspi->current_cs = -1;
cqspi->sclk = 0;
.........
}
调用cqspi_controller_init进行qspi控制器的初始化,如下。
static void cqspi_controller_init(struct cqspi_st *cqspi)
{
cqspi_controller_enable(cqspi, 0);
/* Configure the remap address register, no remap */
writel(0, cqspi->iobase + CQSPI_REG_REMAP);
/* Disable all interrupts. */
writel(0, cqspi->iobase + CQSPI_REG_IRQMASK);
/* Configure the SRAM split to 1:1 . */
writel(cqspi->fifo_depth / 2, cqspi->iobase + CQSPI_REG_SRAMPARTITION);
/* Load indirect trigger address. */
writel(cqspi->trigger_address,
cqspi->iobase + CQSPI_REG_INDIRECTTRIGGER);
/* Program read watermark -- 1/2 of the FIFO. */
writel(cqspi->fifo_depth * cqspi->fifo_width / 2,
cqspi->iobase + CQSPI_REG_INDIRECTRDWATERMARK);
/* Program write watermark -- 1/8 of the FIFO. */
writel(cqspi->fifo_depth * cqspi->fifo_width / 8,
cqspi->iobase + CQSPI_REG_INDIRECTWRWATERMARK);
cqspi_controller_enable(cqspi, 1);
}
3.3 cqspi_setup_flash
static int cqspi_probe(struct platform_device *pdev)
{
......
ret = cqspi_setup_flash(cqspi, np);
if (ret) {
dev_err(dev, "Cadence QSPI NOR probe failed %d\n", ret);
goto probe_setup_failed;
}
.....
}
调用cqspi_setup_flash进行flash的初始化设置,flash的基本信息已经在DTS中有所体现。
3.3.1 解析DTS中flash的信息
static int cqspi_setup_flash(struct cqspi_st *cqspi, struct device_node *np)
{
/* Get flash device data */
for_each_available_child_of_node(dev->of_node, np) {
ret = of_property_read_u32(np, "reg", &cs);
if (ret) {
dev_err(dev, "Couldn't determine chip select.\n");
goto err;
}
if (cs >= CQSPI_MAX_CHIPSELECT) {
ret = -EINVAL;
dev_err(dev, "Chip select %d out of range.\n", cs);
goto err;
}
f_pdata = &cqspi->f_pdata[cs];
f_pdata->cqspi = cqspi;
f_pdata->cs = cs;
ret = cqspi_of_get_flash_pdata(pdev, f_pdata, np);
if (ret)
goto err;
nor = &f_pdata->nor;
mtd = &nor->mtd;
mtd->priv = nor;
nor->dev = dev;
spi_nor_set_flash_node(nor, np);
nor->priv = f_pdata;
...........
}
return 0;
..........
}
解析DTS中flash的信息,幅值给f_pdata,最终给到nor->priv里面。
3.3.2 设置struct spi_nor结构体
static int cqspi_setup_flash(struct cqspi_st *cqspi, struct device_node *np)
{
/* Get flash device data */
for_each_available_child_of_node(dev->of_node, np) {
.............
nor = &f_pdata->nor;
......
nor->read_reg = cqspi_read_reg;
nor->write_reg = cqspi_write_reg;
nor->read = cqspi_read;
nor->write = cqspi_write;
nor->erase = cqspi_erase;
nor->prepare = cqspi_prep;
nor->unprepare = cqspi_unprep;
mtd->name = devm_kasprintf(dev, GFP_KERNEL, "%s.%d",
dev_name(dev), cs);
...........
}
return 0;
......
}
设置struct spi_nor结构体,主要设置其几个读/写/擦除等函数。
3.3.3 spi_nor_scan注册struct spi_nor结构体
static int cqspi_setup_flash(struct cqspi_st *cqspi, struct device_node *np)
{
const struct spi_nor_hwcaps hwcaps = {
.mask = SNOR_HWCAPS_READ |
SNOR_HWCAPS_READ_FAST |
SNOR_HWCAPS_READ_1_1_2 |
SNOR_HWCAPS_READ_1_1_4 |
SNOR_HWCAPS_READ_1_1_8 |
SNOR_HWCAPS_PP,
};
/* Get flash device data */
for_each_available_child_of_node(dev->of_node, np) {
...........
ret = spi_nor_scan(nor, NULL, &hwcaps);
if (ret) {
dev_err(dev, "spi_nor_scan cs %d, ret %d\n", cs, ret);
goto err;
}
...........
}
return 0;
..........
}
spi_nor_scan函数是spi-nor framework的入口函数,已经在前面的文章介绍了。
3.3.4 mtd device注册(mtd_device_register)
static int cqspi_setup_flash(struct cqspi_st *cqspi, struct device_node *np)
{
........
/* Get flash device data */
for_each_available_child_of_node(dev->of_node, np) {
...........
ret = mtd_device_register(mtd, NULL, 0);
if (ret) {
dev_err(dev, "mtd_device_register cs %d, ret %d\n", cs, ret);
goto err;
}
f_pdata->registered = true;
}
.........
}
mtd_device_register注册mtd设备,后面有空再聊这个。
4. 几个重要函数
少数据量读写用read_reg/write_reg,大数据量用read/write。读写底层实现使用QSPI控制器实
4.1 cqspi_read_reg(read id/status)
static int cqspi_read_reg(struct spi_nor *nor, u8 opcode, u8 *buf, int len)
{
int ret;
ret = cqspi_set_protocol(nor, 0);
if (!ret)
ret = cqspi_command_read(nor, &opcode, 1, buf, len);
return ret;
}
static int cqspi_command_read(struct spi_nor *nor,
const u8 *txbuf, const unsigned n_tx,
u8 *rxbuf, const unsigned n_rx)
{
struct cqspi_flash_pdata *f_pdata = nor->priv;
struct cqspi_st *cqspi = f_pdata->cqspi;
void __iomem *reg_base = cqspi->iobase;
unsigned int rdreg;
unsigned int reg;
unsigned int read_len;
int status;
if (!n_rx || n_rx > CQSPI_STIG_DATA_LEN_MAX || !rxbuf) {
dev_err(nor->dev, "Invalid input argument, len %d rxbuf 0x%p\n",
n_rx, rxbuf);
return -EINVAL;
}
reg = txbuf[0] << CQSPI_REG_CMDCTRL_OPCODE_LSB;
rdreg = cqspi_calc_rdreg(nor, txbuf[0]);
writel(rdreg, reg_base + CQSPI_REG_RD_INSTR);
reg |= (0x1 << CQSPI_REG_CMDCTRL_RD_EN_LSB);
/* 0 means 1 byte. */
reg |= (((n_rx - 1) & CQSPI_REG_CMDCTRL_RD_BYTES_MASK)
<< CQSPI_REG_CMDCTRL_RD_BYTES_LSB);
status = cqspi_exec_flash_cmd(cqspi, reg);
if (status)
return status;
reg = readl(reg_base + CQSPI_REG_CMDREADDATALOWER);
/* Put the read value into rx_buf */
read_len = (n_rx > 4) ? 4 : n_rx;
memcpy(rxbuf, ®, read_len);
rxbuf += read_len;
if (n_rx > 4) {
reg = readl(reg_base + CQSPI_REG_CMDREADDATAUPPER);
read_len = n_rx - read_len;
memcpy(rxbuf, ®, read_len);
}
return 0;
}
step1 向CQSPI_REG_CMDCTRL(90h)写入opcode,read_len,read_enable,
触发控制器发起总线操作;
step2 从CQSPI_REG_CMDREADDTALOWER(A0h)/CQSPI_REG_CMDREADDTAUPPER(A4h)
读取内容。
4.2 cqspi_write_reg(write status/configuration)
static int cqspi_write_reg(struct spi_nor *nor, u8 opcode, u8 *buf, int len)
{
int ret;
ret = cqspi_set_protocol(nor, 0);
if (!ret)
ret = cqspi_command_write(nor, opcode, buf, len);
return ret;
}
static int cqspi_command_write(struct spi_nor *nor, const u8 opcode,
const u8 *txbuf, const unsigned n_tx)
{
struct cqspi_flash_pdata *f_pdata = nor->priv;
struct cqspi_st *cqspi = f_pdata->cqspi;
void __iomem *reg_base = cqspi->iobase;
unsigned int reg;
unsigned int data;
int ret;
if (n_tx > 4 || (n_tx && !txbuf)) {
dev_err(nor->dev,
"Invalid input argument, cmdlen %d txbuf 0x%p\n",
n_tx, txbuf);
return -EINVAL;
}
reg = opcode << CQSPI_REG_CMDCTRL_OPCODE_LSB;
if (n_tx) {
reg |= (0x1 << CQSPI_REG_CMDCTRL_WR_EN_LSB);
reg |= ((n_tx - 1) & CQSPI_REG_CMDCTRL_WR_BYTES_MASK)
<< CQSPI_REG_CMDCTRL_WR_BYTES_LSB;
data = 0;
memcpy(&data, txbuf, n_tx);
writel(data, reg_base + CQSPI_REG_CMDWRITEDATALOWER);
}
ret = cqspi_exec_flash_cmd(cqspi, reg);
return ret;
}
step1 向CQSPI_REG_CMDWRITEDATALOWER(A8h)/CQSPI_REG_CMDWRITEDATAUPPER(ACh)
写入数据;
step2 向CQSPI_REG_CMDCTRL(90h)写入opcode,write_len,write_enable,
触发控制器发起总线操作。
4.3 cqspi_read/read sfdp
static ssize_t cqspi_read(struct spi_nor *nor, loff_t from,
size_t len, u_char *buf)
{
int ret;
ret = cqspi_set_protocol(nor, 1);
if (ret)
return ret;
ret = cqspi_indirect_read_setup(nor, from);
if (ret)
return ret;
ret = cqspi_indirect_read_execute(nor, buf, len);
if (ret)
return ret;
return len;
}
(1) DAC模式
step1 向CQSPI_REG_RD_INSTR(04h)写入opcode、data/address/instruction使用的总线类型;
step2 向CQSPI_REG_SIZE(14h)写入地址长度(3byte/4byte);
step3 从ahbbase + from 直接拷贝内容到缓存。
(2) INDAC模式
step1 向CQSPI_REG_INDIRECTTRIGGER(1Ch)写入INDAC模式访问的基地址,用于区分INDAC和DAC访问的地址区间;
step2 向CQSPI_REG_INDIRECTRDSTARTADDR(68h)写入INDAC模式访问的FLASH片内偏移地址;
step3 向CQSPI_REG_RD_INSTR(04h)写入opcode、data/address/instruction使用的总线类型;
step4 向CQSPI_REG_SIZE(14h)写入地址长度(3byte/4byte);
step5 向CQSPI_REG_INDIRECTRDBYTES(6Ch)写入要读取的数据长度;
step6 写CQSPI_REG_INDIRECTRD(60h)触发INDAC访问;
step7 读CQSPI_REG_SDRAMLEVEL(2Ch)检查SRAM中读到的数据长度;
step8 根据数据长度,从ahbbase读取SRAM中的内容;
判断SRAM中数据是否读完,如果是,执行step9,否则执行step7;
step9 读CQSPI_REG_INDIRECTRD(60h)确认操作是否完成;
step10 向CQSPI_REG_INDIRECTRD(60h)写清读操作完成标志。
4.4 cqspi_write
static ssize_t cqspi_write(struct spi_nor *nor, loff_t to,
size_t len, const u_char *buf)
{
int ret;
ret = cqspi_set_protocol(nor, 0);
if (ret)
return ret;
ret = cqspi_indirect_write_setup(nor, to);
if (ret)
return ret;
ret = cqspi_indirect_write_execute(nor, buf, len);
if (ret)
return ret;
return len;
}
(1) DAC模式
step1 向CQSPI_REG_WR_INSTR(08h)写入opcode,data/address采用默认的总线类型SIO模式;
step2 向CQSPI_REG_SIZE(14h)写入地址长度(3byte/4byte);
step3 向ahbbase + to 直接拷贝要写入的内容。
(2) INDAC模式
step1 向CQSPI_REG_INDIRECTTRIGGER(1Ch)写入INDAC模式访问的基地址,用于区分INDAC和DAC访问的地址区间;
step2 向CQSPI_REG_INDIRECTRDSTARTADDR(68h)写入INDAC模式访问的FLASH片内偏移地址;
step3 向CQSPI_REG_WR_INSTR(08h)写入opcode,data/address采用默认的总线类型SIO模式;
step4 向CQSPI_REG_SIZE(14h)写入地址长度(3byte/4byte);
step5 向CQSPI_REG_INDIRECTWRBYTES(7Ch)写入要写入的数据长度;
step6 写CQSPI_REG_INDIRECTWR(70h)触发INDAC写访问;
step7 向ahbbase拷入一段要写入的数据内容;
step8 读CQSPI_REG_SDRAMLEVEL(2Ch),确认SRAM是否写满;
判断SRAM中数据是否发完,如果是,执行step9,否则执行step7;
step9 读CQSPI_REG_INDIRECTWR(70h)确认写操作是否完成;
step10 向CQSPI_REG_INDIRECTWR(70h)写清写操作完成标志。
5. 使用sysfs调试
注册一个sysfs
static int cqspi_probe(struct platform_device *pdev)
{
......
cqspi_wait_idle(cqspi);
cqspi_controller_init(cqspi);
cqspi->current_cs = -1;
cqspi->sclk = 0;
cqspi_create_sysfs(cqspi);
ret = cqspi_setup_flash(cqspi, np);
if (ret) {
dev_err(dev, "Cadence QSPI NOR probe failed %d\n", ret);
goto probe_setup_failed;
}
.....
}
static u32 cqspi_dbg_ctrl = 0;
static ssize_t cqspi_dbg_show(struct device *dev, struct device_attribute *attr, char *buf)
{
struct cqspi_st *cqspi = g_cqspi;
ssize_t rc = 0;
if (cqspi_dbg_ctrl & 0x1) {
rc = cqspi_dump_regs(cqspi, buf);
} else {
rc = cqspi_dump_controller(cqspi, buf);
}
cqspi_dbg_ctrl++;
return rc;
}
static int cqspi_read_data_to_file(struct device *dev, u8 *data, size_t len)
{
int ret;
struct file *fp;
mm_segment_t old_fs;
loff_t pos;
char *tmp_file = "/tmp/rd.bin";
fp = filp_open(tmp_file, O_RDWR | O_CREAT, 0644);
if (IS_ERR(fp)) {
ret = PTR_ERR(fp);
dev_info(dev, "open %s failed,err = %d\n", tmp_file, ret);
return ret;
}
old_fs = get_fs();
set_fs(KERNEL_DS);
pos = fp->f_pos;
ret = kernel_write(fp, data, len, &pos);
fp->f_pos = pos;
set_fs(old_fs);
filp_close(fp, NULL);
dev_info(dev, "read to file %s ret 0x%x, size 0x%llx\n", tmp_file, ret, pos);
return ret;
}
static ssize_t cqspi_dbg_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count)
{
struct spi_nor *nor = &g_cqspi->f_pdata[0].nor;
struct device *curr_dev = nor->dev;
struct cqspi_sysfs_cmd cmd_arr[] = {
{SYSFS_CMD_READ_REG, "readreg"}, {SYSFS_CMD_WRITE_REG, "writereg"},
{SYSFS_CMD_READ_AHB, "readahb"}, {SYSFS_CMD_READ_ID, "readid"},
{SYSFS_CMD_READ_STATUS, "readstatus"}, {SYSFS_CMD_ERASE_DATA, "erase"},
{SYSFS_CMD_READ_DATA, "readdata"}, {SYSFS_CMD_WRITE_DATA, "writedata"} };
char *p = NULL;
char *endp = NULL;
u8 *test_buf = NULL;
u32 op_offset, op_size, rd_val, wr_val, wr_pattern;
u32 erasesize, sectors, i;
bool found = false;
int ret;
u8 id[6];
u8 val;
for (i = 0; i < ARRAY_SIZE(cmd_arr); i++) {
if (cmd_arr[i].key_str == NULL) {
break;
}
p = strstr(buf, cmd_arr[i].key_str);
if (p) {
p += strlen(cmd_arr[i].key_str);
found = true;
break;
}
}
if (!found) {
dev_info(curr_dev, "Current commands supported:\n");
dev_info(curr_dev, "readreg <offset>\n");
dev_info(curr_dev, "writereg <offset> <value>\n");
dev_info(curr_dev, "readahb <offset>\n");
dev_info(curr_dev, "readid 0\n");
dev_info(curr_dev, "readstatus 0\n");
dev_info(curr_dev, "erase <offset> <length>\n");
dev_info(curr_dev, "readdata <offset> <length>\n");
dev_info(curr_dev, "writedata <offset> <length> <pattern>\n");
return count;
}
op_offset = (u32)simple_strtoul(p + 1, &endp, 0);
if (op_offset & 0x3) {
dev_info(curr_dev, "offset 0x%x not align to 0x4\n", op_offset);
return count;
}
switch (cmd_arr[i].cmd) {
case SYSFS_CMD_READ_REG:
rd_val = readl(g_cqspi->iobase + op_offset);
dev_info(curr_dev, "reg [0x%02x] 0x%x\n", op_offset, rd_val);
break;
case SYSFS_CMD_WRITE_REG:
if (endp == NULL) {
dev_info(curr_dev, "not enough para, wr_val needed\n");
return count;
}
wr_val = (u32)simple_strtoul(endp + 1, &endp, 0);
writel(wr_val, g_cqspi->iobase + op_offset);
dev_info(curr_dev, "wr reg [0x%02x] 0x%x\n", op_offset, wr_val);
break;
case SYSFS_CMD_READ_AHB:
rd_val = readl(g_cqspi->ahb_base + op_offset);
dev_info(curr_dev, "ahb [0x%02x] 0x%x\n", op_offset, rd_val);
break;
case SYSFS_CMD_READ_ID:
ret = cqspi_read_reg(nor, SPINOR_OP_RDID, id, sizeof(id));
if (ret < 0) {
dev_info(curr_dev, "error %d reading ID\n", ret);
} else {
dev_info(curr_dev, "ID: %02x %02x %02x %02x %02x %02x\n",
id[0], id[1], id[2], id[3], id[4], id[5]);
}
break;
case SYSFS_CMD_READ_STATUS:
ret = cqspi_read_reg(nor, SPINOR_OP_RDSR, &val, 1);
if (ret < 0) {
dev_info(curr_dev, "error %d reading SR\n", ret);
} else {
dev_info(curr_dev, "SR 0x%x\n", val);
}
ret = cqspi_read_reg(nor, SPINOR_OP_RDCR, &val, 1);
if (ret < 0) {
dev_info(curr_dev, "error %d reading CR\n", ret);
} else {
dev_info(curr_dev, "CR 0x%x\n", val);
}
ret = cqspi_read_reg(nor, SPINOR_OP_RDFSR, &val, 1);
if (ret < 0) {
dev_info(curr_dev, "error %d reading FSR\n", ret);
} else {
dev_info(curr_dev, "FSR 0x%x\n", val);
}
break;
case SYSFS_CMD_ERASE_DATA:
if (endp == NULL) {
dev_info(curr_dev, "not enough para, op_size needed\n");
break;
}
op_size = (u32)simple_strtoul(endp + 1, &endp, 0);
erasesize = nor->mtd.erasesize;
dev_info(curr_dev, "size = 0x%x, erasesize = 0x%x\n", op_size, erasesize);
sectors = (erasesize != 0) ? (op_size / erasesize) : (op_size / SPINOR_DEFAULT_BLOCK_SIZE);
for (i = 0; i < sectors; i++) {
ret = cqspi_erase(nor, op_offset + i * erasesize);
if (ret < 0) {
dev_info(curr_dev, "error %d erase sector %d\n", ret, i);
break;
}
mdelay(200); /* not accurate */
}
if (i == sectors) {
dev_info(curr_dev, "erase %d sectors done\n", sectors);
}
break;
case SYSFS_CMD_READ_DATA:
if (endp == NULL) {
dev_info(curr_dev, "not enough para, op_size needed\n");
break;
}
op_size = (u32)simple_strtoul(endp + 1, &endp, 0);
if (op_size > SYSFS_TEST_BUFFER_SIZE) {
dev_info(curr_dev, "op_size too large\n");
break;
}
test_buf = (u8 *)kzalloc(SYSFS_TEST_BUFFER_SIZE, GFP_KERNEL);
if (test_buf == NULL) {
dev_info(curr_dev, "malloc failed\n");
break;
}
ret = cqspi_read(nor, op_offset, op_size, test_buf);
if (ret != op_size) {
dev_info(curr_dev, "error %d read data\n", ret);
} else {
cqspi_read_data_to_file(curr_dev, test_buf, op_size);
}
kfree(test_buf);
break;
case SYSFS_CMD_WRITE_DATA:
if (endp == NULL) {
dev_info(curr_dev, "not enough para, op_size & wr_pattern needed\n");
break;
}
op_size = (u32)simple_strtoul(endp + 1, &endp, 0);
dev_info(curr_dev, "size = 0x%x\n", op_size);
if (op_size > SYSFS_TEST_BUFFER_SIZE) {
dev_info(curr_dev, "op_size too large\n");
break;
}
if (endp == NULL) {
dev_info(dev, "not enough para, wr_pattern needed\n");
break;
}
wr_pattern = (u32)simple_strtoul(endp + 1, &endp, 0);
dev_info(curr_dev, "pattern = 0x%x\n", wr_pattern);
test_buf = (u8 *)kmalloc(SYSFS_TEST_BUFFER_SIZE, GFP_KERNEL);
if (test_buf == NULL) {
dev_info(curr_dev, "malloc failed\n");
break;
}
if (wr_pattern == 1) {
memset(test_buf, 0xa5, op_size);
} else if (wr_pattern == 2) {
memset(test_buf, 0x81, op_size);
} else {
for (i = 0; i < op_size; i++) {
test_buf[i] = i & 0xff;
}
}
ret = cqspi_write(nor, op_offset, op_size, test_buf);
if (ret != op_size) {
dev_info(curr_dev, "error %d write data\n", ret);
}
kfree(test_buf);
break;
default:
break;
}
return count;
}
static DEVICE_ATTR(cqspidbg, S_IRUGO | S_IWUSR, cqspi_dbg_show, cqspi_dbg_store);
static struct attribute *cqspidbg_attrs[] = {
&dev_attr_cqspidbg.attr,
NULL,
};
static struct attribute_group cqspidbg_attr_group = {
.attrs = cqspidbg_attrs,
};
struct kobject *cqspi_node_device = NULL;
static int cqspi_create_sysfs(struct cqspi_st *cqspi)
{
struct device *dev = &cqspi->pdev->dev;
int ret;
g_cqspi = cqspi;
cqspi_node_device = kobject_create_and_add("cdns_qspi", NULL);
if (cqspi_node_device == NULL) {
dev_err(dev, "create sysfs node failed\n");
return -ENOMEM;
}
ret = sysfs_create_group(cqspi_node_device, &cqspidbg_attr_group);
if (ret) {
dev_err(dev, "sysfs_create_group failed, ret %d\n", ret);
kobject_put(cqspi_node_device);
cqspi_node_device = NULL;
}
return ret;
}
(1) cat /sys/cdns_qspi/cqspidbg
dump寄存器或者驱动控制块
(2) echo readreg <offset> > /sys/cdns_qspi/cqspidbg
读寄存器
(3) echo writereg <offset> <value> > /sys/cdns_qspi/cqspidbg
写寄存器
(4) echo readid 0 > /sys/cdns_qspi/cqspidbg
读devid
(5) echo readstatus 0 > /sys/cdns_qspi/cqspidbg
读status/config/flag寄存器
(6) echo erase <offset> <length> > /sys/cdns_qspi/cqspidbg
擦除指定偏移和长度的sector区域
(7) echo readdata <offset> <length> > /sys/cdns_qspi/cqspidbg
从指定偏移位置读指定长度的内容到临时文件
(8) echo writedata <offset> <length> <pattern> > /sys/cdns_qspi/cqspidbg
向指定偏移位置写入指定长度的内容,具体内容由pattern决定