Linux MTD子系统学习(二)

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)
1
2
该函数的主要功能是根据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;
}

————————————————
版权声明:本文为CSDN博主「楓潇潇」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/u013836909/article/details/93299740

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值