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, &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决定   

### 回答1: 《Cadence Allegro 16.6高级教程.pdf》是一份关于Cadence Allegro 16.6软件的高级教程文档。Cadence Allegro是一款专业的电子设计自动化(EDA)软件,用于电路板设计和封装布局。这份教程包含了该软件的高级使用方法和技巧。 教程的内容主要涉及Cadence Allegro 16.6软件的各种高级功能,帮助用户更深入地了解和掌握该软件。它包括了多个章节,每个章节都介绍了不同的主题和功能。例如,教程中可能包含了关于高速信号处理、嵌入式设计、电源管理和布局优化等方面的内容。 通过学习这份教程,用户可以学习到如何更高效地使用Cadence Allegro 16.6软件来设计和布局电路板。教程中可能会介绍一些高级的技巧和工具,例如信号完整性分析、功耗优化和高速布线等。 对于已经熟悉Cadence Allegro软件的用户来说,这份高级教程能够帮助他们进一步提升其使用技能,从而更好地应对复杂的设计需求。对于新手来说,这份教程也能够帮助他们了解Cadence Allegro软件的高级功能,提升自己的设计能力。 总之,《Cadence Allegro 16.6高级教程.pdf》是一份非常有价值的教程资源,可以帮助用户更好地使用Cadence Allegro 16.6软件来进行电路板设计和封装布局。无论是对于已经熟悉该软件的用户还是对于新手来说,这份教程都是一个很好的学习资料。 ### 回答2: cadence-allegro16.6高级教程.pdf是一本关于Cadence Allegro 16.6高级版的教程手册。Cadence Allegro是一款专业的电子设计自动化(EDA)工具,被广泛用于电子产品的设计和制造过程中。 这本教程手册主要是为那些已经有一定经验的Cadence Allegro用户准备的。它提供了更深入的指导,以帮助用户更好地了解和应用这个高级版软件的功能和特性。 在这本教程手册中,读者将学习如何使用Cadence Allegro的高级功能,比如自定义设计规则和约束,高速信号传输和时序分析等等。这些功能将帮助用户更好地设计和优化他们的电路板和布局。 此外,教程手册还介绍了一些实际的案例和示例,将理论知识与实际应用相结合。读者可以通过这些案例来学习如何解决设计中的常见问题,并了解一些最佳实践和技巧。 总而言之,cadence-allegro16.6高级教程.pdf是一本针对已有Cadence Allegro使用经验的用户的教程手册。它将帮助读者深入了解和应用Cadence Allegro 16.6高级版的功能,提高他们的设计水平和效率。无论是对于新手还是有经验的用户,这本教程手册都是一个宝贵的学习资源。 ### 回答3: 《Cadence-Allegro 16.6高级教程.pdf》是一本关于Cadence公司推出的电子设计自动化软件Allegro 16.6高级应用教程的电子书。Allegro是一款广泛应用于PCB设计领域的软件,拥有强大的功能和工具。这本教程旨在帮助用户进一步掌握Allegro的高级功能,并提供实用的技巧和指南。 这本教程包含了Allegro 16.6中的各种高级功能,比如高速设计、信号完整性分析、电磁兼容性分析等。它详细介绍了如何在Allegro中进行高速设计,包括布线规则、信号同步、差分/单端布线等。同时,它还介绍了如何使用信号完整性分析工具来验证设计的电气性能,并解决信号完整性问题。 此外,本教程还介绍了如何进行电磁兼容性分析,以确保设计在电磁干扰环境下具有良好的性能。它介绍了各种电磁兼容性分析工具的使用方法,以及如何优化设计以满足电磁兼容性要求。 除了这些高级功能,本教程还涵盖了其他各种主题,如印刷电路板布局、封装设计、仿真和验证等。它提供了一系列实际案例和使用技巧,帮助读者理解和应用Allegro的各种功能。 总之,《Cadence-Allegro 16.6高级教程.pdf》是一本帮助用户深入了解和应用Allegro 16.6高级功能的宝贵资料,对于PCB设计人员和学习者来说都是非常有价值的参考。
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值