Linux内核4.14版本——SPI NOR子系统(3)——cadence-quadspi.c分析
目录
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, ®, 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决定
Linux内核4.14版本——SPI NOR子系统(3)——cadence-quadspi.c分析相关推荐
- 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 ...
- 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_ ...
- Linux内核4.14版本——watchdog看门狗框架分析
目录 0 简介 1. 设备的注册 1.1 dw_wdt_drv_probe 1.2 watchdog_register_device 1.3 __watchdog_register_device 1. ...
- 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版本移植的,基 ...
- Linux内核4.14版本——alsa框架分析(1)—alsa简介
目录 一,ALSA声音编程介绍 二,ALSA历史 三,数字音频基础 四,ALSA基础 五,ALSA体系结构 六,设备命名 七,声音缓存和数据传输 八,Over and Under Run 九,一个典型 ...
- Linux内核4.14版本——drm框架分析(1)——drm简介
目录 1. DRM简介(Direct Rendering Manager) 1.1 DRM发展历史 1.2 DRM架构对比FB架构优势 1.3 DRM图形显示框架 1.4 DRM图形显示框架涉及元素 ...
- 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 ...
- 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控 ...
- 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 ...
最新文章
- iOS架构设计-URL缓存(上)
- php zip压缩命令,php zip压缩文件
- BZOJ 1370: [Baltic2003]Gang团伙 [并查集 拆点 | 种类并查集WA]
- 【Network Security!】深入浅出ARP协议使用中间人截获密码
- LBS推荐系统的设计方法
- 如何将读书与自己的生活工作结合起来?
- 集群管理工具KafkaAdminClient——改造
- H3C配置PPP协议
- G29Prescan半实物仿真流程
- 绘制函数z = x2 + y2所表示的三维网格图
- 【STM32】IIC的基本原理(实例:普通IO口模拟IIC时序读取24C02)
- 译文:一个采用 Three.js 的 3D 动画场景制作:飞行者
- Ps光速制作文字矢量图
- python 以图搜图1688_python 以图搜图
- JZOJ100047. 【NOIP2017提高A组模拟7.14】基因变异
- 京东获取商品历史价格信息 API 返回值说明
- svc预测概率_Kaggle平台Titanic生存率预测项目(TOP3%)
- 千里走单骑:06-北京到上海骑记--Day5.风雨回家路
- BroadcastReceiver使用之一(常驻BroadcastReceiver接收短信)
- 消防系统设计市场现状及未来发展趋势