Linux MTD子系统学习(二)

Linux MTD子系统学习(二)

3 Linux MTD spi-nor驱动分析

3.1 spi-nor设备驱动框架

在这里插入图片描述

3.2 spi-nor设备注册

如果希望一个spi设备可以在linux系统下很好的工作,除了写驱动,还要向内核申明和注册这个spi设备。目前有两种方法向内核注册一个spi设备。在稍微老点版本的内核(2.6.xx)中通过向内核注册struct spi_board_info对象,来申明一个spi设备。在比较新的内核中(3.xx)使用device tree的方式向内核申明并注册一个spi设备的。无论使用哪种方式,其实最终的目的就是为了建立一个struct spi_deivce对象,并注册到spi子系统中。下面就结合代码来看看是如何注册一个spi设备到内核中的。

3.2.1 spi_board_info的方式
struct spi_board_info {
	/* the device name and module name are coupled, like platform_bus;
	 * "modalias" is normally the driver name.
	 *
	 * platform_data goes to spi_device.dev.platform_data,
	 * controller_data goes to spi_device.controller_data,
	 * device properties are copied and attached to spi_device,
	 * irq is copied too
	 */
	char		modalias[SPI_NAME_SIZE];
	const void	*platform_data;
	const struct property_entry *properties;
	void		*controller_data;
	int		irq;

	/* slower signaling on noisy or low voltage boards */
	u32		max_speed_hz;


	/* bus_num is board specific and matches the bus_num of some
	 * spi_controller that will probably be registered later.
	 *
	 * chip_select reflects how this chip is wired to that master;
	 * it's less than num_chipselect.
	 */
	u16		bus_num;
	u16		chip_select;

	/* mode becomes spi_device.mode, and is essential for chips
	 * where the default of SPI_CS_HIGH = 0 is wrong.
	 */
	u16		mode;

	/* ... may need additional spi_device chip config data here.
	 * avoid stuff protocol drivers can set; but include stuff
	 * needed to behave without being bound to a driver:
	 *  - quirks like clock rate mattering when not selected
	 */
};

下面就结合某一个板级代码讲解struct spi_board_info各个字段的含义,以及如何在板级代码中注册struct spi_board_info。

static struct spi_board_info xxx_spi_nor_device[] = {
{
         .modalias = "m25p80",	//spi设备名字,设备驱动探测时会用到该项
          .max_speed_hz = 25000000,      /* max spi clock (SCK) speed in HZ */
          .bus_num = 1, //记录该spi设备是连接在哪个spi总线上的,所在总线的编号
          .chip_select = 1,//该spi设备的片选编号
          .mode = SPI_MODE_0,	//此spi设备支持spi总线的工作模式
          .platform_data = NULL,	//可存放flash分区表
},
{},
};

spi设备注册过程:

  • spi_register_board_info()—>spi_match_master_to_boardinfo()—>spi_new_device()

  • spi_register_board_info:

      int spi_register_board_info(struct spi_board_info const *info, unsigned n)
      {
      	struct boardinfo *bi;
      	int i;
      
      	if (!n)
      		return 0;
      
      	bi = kcalloc(n, sizeof(*bi), GFP_KERNEL);
      	if (!bi)
      		return -ENOMEM;
      
      	for (i = 0; i < n; i++, bi++, info++) {
      		struct spi_controller *ctlr;
      
      		memcpy(&bi->board_info, info, sizeof(*info));
      		if (info->properties) {
      			bi->board_info.properties =
      					property_entries_dup(info->properties);
      			if (IS_ERR(bi->board_info.properties))
      				return PTR_ERR(bi->board_info.properties);
      		}
      
      		mutex_lock(&board_lock);
      		list_add_tail(&bi->list, &board_list);
      
      	//遍历spi_controller_list链表,该链表记录所有的spi控制器
      		list_for_each_entry(ctlr, &spi_controller_list, list)
      			spi_match_controller_to_boardinfo(ctlr, &bi->board_info);
      		mutex_unlock(&board_lock);
      	}
      
      	return 0;
      }
    
    • spi_match_master_to_boardinfo:

        static void spi_match_controller_to_boardinfo(struct spi_controller *ctlr,
        					      struct spi_board_info *bi)
        {	
        	struct spi_device *dev;
        
        	//设备指定的控制器编号与控制器编号相等,则注册spi设备
        	if (ctlr->bus_num != bi->bus_num)
        		return;
        
        	dev = spi_new_device(ctlr, bi); //注册spi设备
        	if (!dev)
        		dev_err(ctlr->dev.parent, "can't create new device for %s\n",
        			bi->modalias);
        }
      
  • spi_new_device:

      struct spi_device *spi_new_device(struct spi_controller *ctlr,
      			  struct spi_board_info *chip)
      {
      	struct spi_device	*proxy;
      	int			status;
      
      	/* NOTE:  caller did any chip->bus_num checks necessary.
      	 *
      	 * Also, unless we change the return value convention to use
      	 * error-or-pointer (not NULL-or-pointer), troubleshootability
      	 * suggests syslogged diagnostics are best here (ugh).
      	 */
      
      	proxy = spi_alloc_device(ctlr);
      	if (!proxy)
      		return NULL;
      
      	WARN_ON(strlen(chip->modalias) >= sizeof(proxy->modalias));
      
      	proxy->chip_select = chip->chip_select;
      	proxy->max_speed_hz = chip->max_speed_hz;
      	proxy->mode = chip->mode;
      	proxy->irq = chip->irq;
      	strlcpy(proxy->modalias, chip->modalias, sizeof(proxy->modalias));
      	proxy->dev.platform_data = (void *) chip->platform_data;
      	proxy->controller_data = chip->controller_data;
      	proxy->controller_state = NULL;
      
      	if (chip->properties) {
      		status = device_add_properties(&proxy->dev, chip->properties);
      		if (status) {
      			dev_err(&ctlr->dev,
      				"failed to add properties to '%s': %d\n",
      				chip->modalias, status);
      			goto err_dev_put;
      		}
      	}
      
      	status = spi_add_device(proxy);
      	if (status < 0)
      		goto err_remove_props;
      
      	return proxy;
      
      	err_remove_props:
      	if (chip->properties)
      		device_remove_properties(&proy->dev);
      	err_dev_put:
      	spi_dev_put(proxy);
      	return NULL;
      }
    
3.2.2 device_tree方式

下面以某一个平台,讲解如何使用device tree来向内核中添加一个spi设备,首先看下面的device tree代码片段kernel/arch/arm/boot/dts/xxx.dts:
&spi0 {
status = “okay”;
spi-flash@0 {
compatible = “m25p80”, “jedec,spi-nor”;
spi-max-frequency = <24000000>;
reg = <0>;
};
};

在spi控制器的每一个子节点,都会被内核解析成一个spi设备,最后生成一个struct spi_device,并注册到内核中,我们这个节点也不例外。
这里的spi-flash节点就会被解析成一个struct spi_device对象。那么在什么时候这些子节点被解析成呢?这些子节点是在它的父节点对应的控制器被注册时解析的,也就是说是在调用spi_register_master()向内核注册一个spi控制器时,在这个函数中被解析。这个函数的的最后调用了of_register_spi_devices(master),这个函数的主要目的就是遍历struct spi_master对象所对应的device tree节点的所有子节点,并使用子节点中的属性信息创建对应的struct spi_device,然后注册至内核中。

3.3 spi-nor设备驱动

上面的两种方法注册spi设备,讲的都是m25p80这个spi flash设备,spi设备的驱动,我们也讲解m25p80的驱动。代码在kernel/drivers/mtd/devices/m25p80.c。

static struct spi_driver m25p80_driver = {
	.driver = {
		.name	= "m25p80",			//驱动名
		.of_match_table = m25p_of_table,//与设备树匹配表
	},
	.id_table	= m25p_ids,		//设备与驱动相关的数据
	.probe	= m25p_probe,	//探测函数
	.remove	= m25p_remove,	//移除函数

	/* REVISIT: many of these chips have deep power-down modes, which
	 * should clearly be entered on suspend() to minimize power use.
	 * And also when they're otherwise idle...
	 */
};

module_spi_driver(m25p80_driver);

首先看一下struct spi_driver.id_table,这个项会在struct spi_device和struct spi_driver对象进行匹配时使用到。id_table就是struct spi_device_id数组。我们先看struct spi_device_id结构的原型:

struct spi_device_id {
	char name[SPI_NAME_SIZE];
	kernel_ulong_t driver_data;	/* Data private to the driver */
};

示例如下:

static const struct spi_device_id m25p_ids[] = {
	/*
	 * Allow non-DT platform devices to bind to the "spi-nor" modalias, and
	 * hack around the fact that the SPI core does not provide uevent
	 * matching for .of_match_table
	 */
	{"spi-nor"},

	/*
	 * Entries not used in DTs that should be safe to drop after replacing
	 * them with "spi-nor" in platform data.
	 */
	{"s25sl064a"},	{"w25x16"},	{"m25p10"},	{"m25px64"},

	/*
	 * Entries that were used in DTs without "jedec,spi-nor" fallback and
	 * should be kept for backward compatibility.
	 */
	{"at25df321a"},	{"at25df641"},	{"at26df081a"},
	{"mx25l4005a"},	{"mx25l1606e"},	{"mx25l6405d"},	{"mx25l12805d"},
	{"mx25l25635e"},{"mx66l51235l"},
	{"n25q064"},	{"n25q128a11"},	{"n25q128a13"},	{"n25q512a"},
	{"s25fl256s1"},	{"s25fl512s"},	{"s25sl12801"},	{"s25fl008k"},
	{"s25fl064k"},
	{"sst25vf040b"},{"sst25vf016b"},{"sst25vf032b"},{"sst25wf040"},
	{"m25p40"},	{"m25p80"},	{"m25p16"},	{"m25p32"},
	{"m25p64"},	{"m25p128"},
	{"w25x80"},	{"w25x32"},	{"w25q32"},	{"w25q32dw"},
	{"w25q80bl"},	{"w25q128"},	{"w25q256"},

	/* Flashes that can't be detected using JEDEC */
	{"m25p05-nonjedec"},	{"m25p10-nonjedec"},	{"m25p20-nonjedec"},
	{"m25p40-nonjedec"},	{"m25p80-nonjedec"},	{"m25p16-nonjedec"},
	{"m25p32-nonjedec"},	{"m25p64-nonjedec"},	{"m25p128-nonjedec"},

	/* Everspin MRAMs (non-JEDEC) */
	{ "mr25h256" }, /* 256 Kib, 40 MHz */
	{ "mr25h10" },  /*   1 Mib, 40 MHz */
	{ "mr25h40" },  /*   4 Mib, 40 MHz */

	{ },
};

3.4 spi-nor 注册过程

3.4.1 m25p_prob

源码:kernel/drivers/mtd/devices/m25p80.c

/*
 * board specific setup should have ensured the SPI clock used here
 * matches what the READ command supports, at least until this driver
 * understands FAST_READ (for clocks over 25 MHz).
 */
static int m25p_probe(struct spi_device *spi)
{
	struct flash_platform_data	*data;
	struct m25p *flash;
	struct spi_nor *nor;
	struct spi_nor_hwcaps hwcaps = {
		.mask = SNOR_HWCAPS_READ |
			SNOR_HWCAPS_READ_FAST |
			SNOR_HWCAPS_PP,
	};
	char *flash_name;
	int ret;

	data = dev_get_platdata(&spi->dev);//获取平台数据,可为分区表

	flash = devm_kzalloc(&spi->dev, sizeof(*flash), GFP_KERNEL);
	if (!flash)
		return -ENOMEM;

	nor = &flash->spi_nor;

//以下四个函数是最终的flash的读写函数,
//其将调用控制器驱动函数,实现数据传输
	/* install the hooks */
	nor->read = m25p80_read;	//nor-flash的读函数
	nor->write = m25p80_write;	//nor-flash的写函数
	nor->write_reg = m25p80_write_reg;	//nor-flash写寄存器函数
	nor->read_reg = m25p80_read_reg;	//nor-flash读寄存器函数

	nor->dev = &spi->dev;
	spi_nor_set_flash_node(nor, spi->dev.of_node);
	nor->priv = flash;	//nor设备的私有数据

	spi_set_drvdata(spi, flash);//配置spi设备的私有数据
	flash->spi = spi;

	if (spi->mode & SPI_RX_QUAD) {
		hwcaps.mask |= SNOR_HWCAPS_READ_1_1_4;

		if (spi->mode & SPI_TX_QUAD)
			hwcaps.mask |= (SNOR_HWCAPS_READ_1_4_4 |
					SNOR_HWCAPS_PP_1_1_4 |
					SNOR_HWCAPS_PP_1_4_4);
	} else if (spi->mode & SPI_RX_DUAL) {
		hwcaps.mask |= SNOR_HWCAPS_READ_1_1_2;

		if (spi->mode & SPI_TX_DUAL)
			hwcaps.mask |= SNOR_HWCAPS_READ_1_2_2;
	}

	if (data && data->name)
		nor->mtd.name = data->name;

	/* For some (historical?) reason many platforms provide two different
	 * names in flash_platform_data: "name" and "type". Quite often name is
	 * set to "m25p80" and then "type" provides a real chip name.
	 * If that's the case, respect "type" and ignore a "name".
	 */
	if (data && data->type)
		flash_name = data->type;
	else if (!strcmp(spi->modalias, "spi-nor"))
		flash_name = NULL; /* auto-detect */
	else
		flash_name = spi->modalias;

	//遍历该驱动支持的nor设备,以确认是否支持当前的设备
	ret = spi_nor_scan(nor, flash_name, &hwcaps);//See:3.4.2
	if (ret)
		return ret;

	//注册MTD设备,See:3.4.3
	return mtd_device_register(&nor->mtd, data ? data->parts : NULL,
				   data ? data->nr_parts : 0);
}
3.4.2 spi_nor_scan

该函数的主要功能是将驱动与设备匹配,配置好该设备的基本信息mtd_info等,供后期mtd设备调用。
源码:drivers/mtd/spi-nor.c

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;

	ret = spi_nor_check(nor);//检查读、写、擦除等基本功能函数是否实现
	if (ret)
		return ret;

	/* Reset SPI protocol for all commands. */
	nor->reg_proto = SNOR_PROTO_1_1_1;
	nor->read_proto = SNOR_PROTO_1_1_1;
	nor->write_proto = SNOR_PROTO_1_1_1;

	if (name)	//指定设备名,则用设备名与驱动支持的设备进行匹配
		info = spi_nor_match_id(name);
	/* Try to auto-detect if chip name wasn't specified or not found */
	if (!info)	//使用flash_id进行匹配
		info = spi_nor_read_id(nor);//获取当前flash_id,与支持的id进行匹配
	if (IS_ERR_OR_NULL(info))
		return -ENOENT;

	/*
	 * If caller has specified name of flash model that can normally be
	 * detected using JEDEC, let's verify it.
	 */
	//存在设备名时,通过flash_id进行二次校验,确保驱动支持该当前flash
	if (name && info->id_len) {
		const struct flash_info *jinfo;

		jinfo = spi_nor_read_id(nor);
		if (IS_ERR(jinfo)) {
			return PTR_ERR(jinfo);
		} else if (jinfo != info) {
			/*
			 * JEDEC knows better, so overwrite platform ID. We
			 * can't trust partitions any longer, but we'll let
			 * mtd apply them anyway, since some partitions may be
			 * marked read-only, and we don't want to lose that
			 * information, even if it's not 100% accurate.
			 */
			dev_warn(dev, "found %s, expected %s\n",
				 jinfo->name, info->name);
			info = jinfo;
		}
	}

/* …………………………..
*…………………………..
*…………………………..
*/
//当前设备mtd_info初始化
	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;

	/* 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;
	}

	/* sst nor chips use AAI word program */
	if (info->flags & SST_WRITE)
		mtd->_write = sst_write;
	else
		mtd->_write = spi_nor_write;

	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;

	if (info->flags & SPI_NOR_NO_ERASE)
		mtd->flags |= MTD_NO_ERASE;

	mtd->dev.parent = dev;
	nor->page_size = params.page_size;
	mtd->writebufsize = nor->page_size;

/* …………………………..
*…………………………..
*…………………………..
*/

	return 0;
}
3.4.3 mtd_device_register

该函数其实为一个宏,其定义如下:
源码:include/linux/mtd/mtd.h

#define mtd_device_register(master, parts, nr_parts)	\
	mtd_device_parse_register(master, NULL, NULL, parts, nr_parts)

该函数的主要功能是根据mtd设备提供的设备信息,结合分区表的信息,建立新分区的mtd_info,添加到mtd_device中,回调块设备的注册函数,注册相关的块设备。
源码:drivers/mtd/mtdcore.c

int mtd_device_parse_register(struct mtd_info *mtd, const char * const *types,
			      struct mtd_part_parser_data *parser_data,
			      const struct mtd_partition *parts,
			      int nr_parts)
{
	struct mtd_partitions parsed;
	int ret;

	mtd_set_dev_defaults(mtd);

	memset(&parsed, 0, sizeof(parsed));

	//解析分区表,See:4.2.1.1
	ret = parse_mtd_partitions(mtd, types, &parsed, parser_data);
	if ((ret < 0 || parsed.nr_parts == 0) && parts && nr_parts) {
		/* Fall back to driver-provided partitions */
		parsed = (struct mtd_partitions){
			.parts		= parts,
			.nr_parts	= nr_parts,
		};
	} else if (ret < 0) {
		/* Didn't come up with parsed OR fallback partitions */
	 pr_info("mtd: failed to find partitions; one or more parsers reports errors (%d)\n", ret);
		/* Don't abort on errors; we can still use unpartitioned MTD */
		memset(&parsed, 0, sizeof(parsed));
	}

	//添加设备分区信息,注册块设备及字符设备,See:4.2.3.1
	ret = mtd_add_device_partitions(mtd, &parsed);
	if (ret)
		goto out;

	/*
	 * FIXME: some drivers unfortunately call this function more than once.
	 * So we have to check if we've already assigned the reboot notifier.
	 *
	 * Generally, we can make multiple calls work for most cases, but it
	 * does cause problems with parse_mtd_partitions() above (e.g.,
	 * cmdlineparts will register partitions more than once).
	 */
	WARN_ONCE(mtd->_reboot && mtd->reboot_notifier.notifier_call,
		  "MTD already registered\n");
	if (mtd->_reboot && !mtd->reboot_notifier.notifier_call) {
		mtd->reboot_notifier.notifier_call = mtd_reboot_notifier;
		register_reboot_notifier(&mtd->reboot_notifier);
	}

out:
	/* Cleanup any parsed partitions */
	mtd_part_parser_cleanup(&parsed);
	return ret;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值