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;
}
————————————————
版权声明:本文为优快云博主「楓潇潇」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.youkuaiyun.com/u013836909/article/details/93299740