系列文章:
Linux spi驱动框架分析(一)
Linux spi驱动框架分析(二)
Linux spi驱动框架分析(三)
Linux spi驱动框架分析(四)
spi_master驱动
spi_master驱动负责最底层的数据收发工作,为了完成数据的收发工作,控制器驱动需要完成以下这些功能:
- 申请必要的硬件资源,例如中断,DMA通道,DMA内存缓冲区等等;
- 配置spi控制器的工作模式和参数,使之可以和相应的设备进行正确的数据交换工作;
- 向spi核心层提供接口,使得上层的设备驱动可以通过核心层访问控制器驱动;
- 配合核心层,完成数据消息队列的排队和处理,直到消息队列变空为止;
控制器的相关信息一般在设备树中描述,驱动程序从设备树中获取硬件资源,用以初始化spi_master,之后调用spi_register_master函数进行注册。
如下图描述了一个spi控制器的设备节点:
在设备树上描述了spi控制器的各种IO资源,如寄存器地址、中断号,片选引脚的gpio等。
下面就来分析spi_register_master函数,函数执行流程图如下所示:
源码分析
spi_register_master函数定义如下:
int spi_register_master(struct spi_master *master)
{
static atomic_t dyn_bus_id = ATOMIC_INIT((1<<15) - 1);
struct device *dev = master->dev.parent;
struct boardinfo *bi;
int status = -ENODEV;
int dynamic = 0;
if (!dev)
return -ENODEV;
//从设备树获取信息,用以初始化spi_master里的num_chipselect、cs_gpios成员
status = of_spi_register_master(master);
if (status)
return status;
if (master->num_chipselect == 0)
return -EINVAL;
//设置总线号
if ((master->bus_num < 0) && master->dev.of_node)
master->bus_num = of_alias_get_id(master->dev.of_node, "spi");
/* convention: dynamically assigned bus IDs count down from the max */
if (master->bus_num < 0) {
/* FIXME switch to an IDR based scheme, something like
* I2C now uses, so we can't run out of "dynamic" IDs
*/
master->bus_num = atomic_dec_return(&dyn_bus_id);
dynamic = 1;
}
//初始化各种锁及链表等
INIT_LIST_HEAD(&master->queue);
spin_lock_init(&master->queue_lock);
spin_lock_init(&master->bus_lock_spinlock);
mutex_init(&master->bus_lock_mutex);
mutex_init(&master->io_mutex);
master->bus_lock_flag = 0;
init_completion(&master->xfer_completion);
......
//如果设置了transfer成员,则不采用消息队列机制,不赞成这么做
if (master->transfer)
dev_info(dev, "master is unqueued, this is deprecated\n");
else {
//初始化消息队列相关
status = spi_master_initialize_queue(master);
if (status) {
device_del(&master->dev);
goto done;
}
}
/* add statistics */
spin_lock_init(&master->statistics.lock);
mutex_lock(&board_lock);
//所有的spi_master都会链接在全局链表spi_master_list里
list_add_tail(&master->list, &spi_master_list);
/* 对于以前不支持设备树的内核,平台相关的spi设备信息是保存在struct spi_board_info结构体里
* 然后调用spi_register_board_info函数统一增添到全局链表board_list里,等待注册spi控制器
* 后,再从board_list链表里取出这些信息,spi_new_device进内核
*/
list_for_each_entry(bi, &board_list, list)
spi_match_master_to_boardinfo(master, &bi->board_info);
mutex_unlock(&board_lock);
/* 注册在设备树里描述的spi设备 */
of_register_spi_devices(master);
......
}
看看在设备树上获取什么信息来初始化spi_master里的num_chipselect、cs_gpios成员,of_spi_register_master:
static int of_spi_register_master(struct spi_master *master)
{
int nb, i, *cs;
struct device_node *np = master->dev.of_node;
if (!np)
return 0;
//通过cs-gpios属性,获取gpio的个数,即片选数
nb = of_gpio_named_count(np, "cs-gpios");
master->num_chipselect = max_t(int, nb, master->num_chipselect);
/* Return error only for an incorrectly formed cs-gpios property */
......
//分配int型数组,长度为片选数,用于保存片选gpio号
cs = devm_kzalloc(&master->dev,
sizeof(int) * master->num_chipselect,
GFP_KERNEL);
master->cs_gpios = cs;
if (!master->cs_gpios)
return -ENOMEM;
for (i = 0; i < master->num_chipselect; i++)
cs[i] = -ENOENT;
//获取片选gpio号并设置cs_gpios
for (i = 0; i < nb; i++)
{
cs[i] = of_get_named_gpio(np, "cs-gpios", i);
}
return 0;
}
前面提到过,实例化spi设备方式之一是在spi控制器的节点下描述设备节点,这些设备节点会在注册spi驱动时,进行解析并注册进内核,调用of_register_spi_devices函数来处理这些设备节点:
static void of_register_spi_devices(struct spi_master *master)
{
struct spi_device *spi;
struct device_node *nc;
if (!master->dev.of_node)
return;
//遍历子节点调用of_register_spi_device函数进行处理
for_each_available_child_of_node(master->dev.of_node, nc) {
if (of_node_test_and_set_flag(nc, OF_POPULATED))
continue;
spi = of_register_spi_device(master, nc);
if (IS_ERR(spi)) {
dev_warn(&master->dev, "Failed to create SPI device for %s\n",
nc->full_name);
of_node_clear_flag(nc, OF_POPULATED);
}
}
}
of_register_spi_device定义如下:
static struct spi_device *
of_register_spi_device(struct spi_master *master, struct device_node *nc)
{
struct spi_device *spi;
int rc;
u32 value;
/* 创建一个spi_device */
spi = spi_alloc_device(master);
......
/* 读取节点中"compatible"属性,并作为name */
rc = of_modalias_node(nc, spi->modalias,
sizeof(spi->modalias));
......
/* 读取节点中"reg属性,并作为片选索引 */
rc = of_property_read_u32(nc, "reg", &value);
......
spi->chip_select = value;
/* 可在设备节点描述"spi-cpha"等相关属性来说明设备的模式 */
if (of_find_property(nc, "spi-cpha", NULL))
spi->mode |= SPI_CPHA;
if (of_find_property(nc, "spi-cpol", NULL))
spi->mode |= SPI_CPOL;
if (of_find_property(nc, "spi-cs-high", NULL))
spi->mode |= SPI_CS_HIGH;
if (of_find_property(nc, "spi-3wire", NULL))
spi->mode |= SPI_3WIRE;
if (of_find_property(nc, "spi-lsb-first", NULL))
spi->mode |= SPI_LSB_FIRST;
/* Device DUAL/QUAD mode */
if (!of_property_read_u32(nc, "spi-tx-bus-width", &value)) {
switch (value) {
case 1:
break;
case 2:
spi->mode |= SPI_TX_DUAL;
break;
case 4:
spi->mode |= SPI_TX_QUAD;
break;
default:
dev_warn(&master->dev,
"spi-tx-bus-width %d not supported\n",
value);
break;
}
}
if (!of_property_read_u32(nc, "spi-rx-bus-width", &value)) {
switch (value) {
case 1:
break;
case 2:
spi->mode |= SPI_RX_DUAL;
break;
case 4:
spi->mode |= SPI_RX_QUAD;
break;
default:
dev_warn(&master->dev,
"spi-rx-bus-width %d not supported\n",
value);
break;
}
}
/* Device speed */
rc = of_property_read_u32(nc, "spi-max-frequency", &value);
if (rc) {
dev_err(&master->dev, "%s has no valid 'spi-max-frequency' property (%d)\n",
nc->full_name, rc);
goto err_out;
}
spi->max_speed_hz = value;
/* Store a pointer to the node in the device structure */
of_node_get(nc);
spi->dev.of_node = nc;
/* 注册新的设备 */
rc = spi_add_device(spi);
if (rc) {
dev_err(&master->dev, "spi_device register error %s\n",
nc->full_name);
goto err_of_node_put;
}
return spi;
err_of_node_put:
of_node_put(nc);
err_out:
spi_dev_put(spi);
return ERR_PTR(rc);
}
of_modalias_node有个细节要注意:
int of_modalias_node(struct device_node *node, char *modalias, int len)
{
const char *compatible, *p;
int cplen;
/* 如,对于“compatible"属性为"invensense,icm20608"的设备节点,最终spi_device的modalias成员
* 为"icm20608"而不是"invensense,icm20608"
*/
compatible = of_get_property(node, "compatible", &cplen);
if (!compatible || strlen(compatible) > cplen)
return -ENODEV;
p = strchr(compatible, ',');
strlcpy(modalias, p ? p + 1 : compatible, len);
return 0;
}