目录

1. 芯片简介

1.1 模块与接口

1.2 访问模式

2. DTS

2.1 reg

2.2 cdns,trigger-address

2.3 匹配DTS的驱动程序入口

3. cqspi_probe

3.1 从第1节DTS中得到配置信息、clk等信息

3.2 初始化控制器(cqspi_controller_init)

3.3 cqspi_setup_flash

3.3.1 解析DTS中flash的信息

3.3.2 设置struct spi_nor结构体

3.3.3 spi_nor_scan注册struct spi_nor结构体

3.3.4 mtd device注册(mtd_device_register)

4. 几个重要函数

4.1 cqspi_read_reg(read id/status)

4.2 cqspi_write_reg(write status/configuration)

4.3 cqspi_read/read sfdp

4.4 cqspi_write

5. 使用sysfs调试


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, &reg, read_len);rxbuf += read_len;if (n_rx > 4) {reg = readl(reg_base + CQSPI_REG_CMDREADDATAUPPER);read_len = n_rx - read_len;memcpy(rxbuf, &reg, 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决定

Linux内核4.14版本——SPI NOR子系统(3)——cadence-quadspi.c分析相关推荐

  1. Linux内核4.14版本——SPI NOR子系统(2)——spi-nor.c分析

    1. 简介 2. spi_nor_scan 2.1 检查结构体struct spi_nor是否合格,匹配支持的nor flash ID得到info 2.1.1 spi_nor_check 2.1.2 ...

  2. Linux内核4.14版本:ARM64的内核启动过程(二)——start_kernel

    目录 1. rest_init 2. init 进程(kernel_init) 2.1 kernel_init_freeable 2.1.1 do_basic_setup 2.1.2 prepare_ ...

  3. Linux内核4.14版本——watchdog看门狗框架分析

    目录 0 简介 1. 设备的注册 1.1 dw_wdt_drv_probe 1.2 watchdog_register_device 1.3 __watchdog_register_device 1. ...

  4. Linux内核4.14版本——DMA Engine框架分析(6)-实战(测试dma驱动)

    1. dw-axi-dmac驱动 2. dma的测试程序 2.1 内核程序 2.2 用户测试程序 1. dw-axi-dmac驱动 dw-axi-dmac驱动4.14版本没有,是从5.4版本移植的,基 ...

  5. Linux内核4.14版本——alsa框架分析(1)—alsa简介

    目录 一,ALSA声音编程介绍 二,ALSA历史 三,数字音频基础 四,ALSA基础 五,ALSA体系结构 六,设备命名 七,声音缓存和数据传输 八,Over and Under Run 九,一个典型 ...

  6. Linux内核4.14版本——drm框架分析(1)——drm简介

    目录 1. DRM简介(Direct Rendering Manager) 1.1 DRM发展历史 1.2 DRM架构对比FB架构优势 1.3 DRM图形显示框架 1.4 DRM图形显示框架涉及元素 ...

  7. Linux内核4.14版本——GPIO子系统(1)——gpiolib分析

    目录 1.简述 2.Gpiolib 相关数据结构分析 2.1 gpio_chip 结构 2.2 gpio_desc 结构 2.3 gpio_device 结构 3 Gpiolib 对接芯片底层 3.1 ...

  8. Linux内核4.14版本——Nand子系统(1)——hisi504_nand.c分析

    1. 简介 2. DTS 3. hisi_nfc_probe函数 3.1 获取dts中的资源 3.2 设置nand-chip结构体中必要的字段 3.3 nand_scan_ident扫描识别nand控 ...

  9. Linux内核4.14版本——mmc框架_软件总体架构

    目录 1. 前言 2. 软件架构 3. 工作流程 4. mmc设备 4.1 mmc type card 4.2 sd type card 4.3 sdio type card 5. mmc协议 5.1 ...

最新文章

  1. iOS架构设计-URL缓存(上)
  2. php zip压缩命令,php zip压缩文件
  3. BZOJ 1370: [Baltic2003]Gang团伙 [并查集 拆点 | 种类并查集WA]
  4. 【Network Security!】深入浅出ARP协议使用中间人截获密码
  5. LBS推荐系统的设计方法
  6. 如何将读书与自己的生活工作结合起来?
  7. 集群管理工具KafkaAdminClient——改造
  8. H3C配置PPP协议
  9. G29Prescan半实物仿真流程
  10. 绘制函数z = x2 + y2所表示的三维网格图
  11. 【STM32】IIC的基本原理(实例:普通IO口模拟IIC时序读取24C02)
  12. 译文:一个采用 Three.js 的 3D 动画场景制作:飞行者
  13. Ps光速制作文字矢量图
  14. python 以图搜图1688_python 以图搜图
  15. JZOJ100047. 【NOIP2017提高A组模拟7.14】基因变异
  16. 京东获取商品历史价格信息 API 返回值说明
  17. svc预测概率_Kaggle平台Titanic生存率预测项目(TOP3%)
  18. 千里走单骑:06-北京到上海骑记--Day5.风雨回家路
  19. BroadcastReceiver使用之一(常驻BroadcastReceiver接收短信)
  20. 消防系统设计市场现状及未来发展趋势

热门文章

  1. 元宇宙是人类文明不可避免的一次内卷
  2. GIF动态图怎么制作
  3. 在Java中求数组的和及平均数
  4. 鹏业安装算量喷淋管件修改问题解答
  5. sandisk TF卡 can not format issue 解决方法
  6. 使用Socket进行设备间点对点连接传输数据
  7. 实例分享!告诉你西门子PLC如何通过MODBUS控制变频器
  8. 2019年注册会计师考试全套高清视频课件百度云网盘免费下载方法
  9. Delphi类型区分——常数
  10. 音乐标签修改器——Mp3tag