http://blog.csdn.net/hustyangju/article/details/20474659
http://www.embedu.org/Column/Column367.htm
以三星平台为例,说明spi的驱动和使用方法
spi-s3c64xx.c文件就是三星exynos5433平台中的spi驱动控制器。
其中一个spi_2的device tree设置如下:
spi_2: spi@14d40000 {
compatible = "samsung,exynos543x-spi";
reg = <0x14d40000 0x100>;
interrupts = <0 434 0>;
dma-mode;
dmas = <&pdma0 13
&pdma0 12>;
dma-names = "tx", "rx";
swap-mode;
#address-cells = <1>;
#size-cells = <0>;
clocks = <&clock 2425>, <&clock 4059>;
clock-names = "spi", "spi_busclk0";
pinctrl-names = "default";
pinctrl-0 = <&spi2_bus>;
status = "disabled";
};
在spi接口的应用方面,以modem控制器驱动为例(exyno5433 AP通过spi与modem通信),说明如何使用spi_2与外部的modem进行通信。
modem驱动中,spi通信端口对应的文件是modem_link_device_spi.c 文件。
其platform驱动的设置如下:
static const struct of_device_id if_spi_match_table[] = {
{ .compatible = "if_spi_comp",
},
{}
};
static struct spi_driver if_spi_driver = {
.probe = if_spi_probe,
.remove = if_spi_remove,
.driver = {
.name = "if_spi_driver",
.of_match_table = if_spi_match_table,
.owner = THIS_MODULE,
},
};
device tree的设置如下:
spi_2: spi@14d40000 {
status = "okay";
/delete-property/ dma-mode;
/delete-property/ dmas;
/delete-property/ dma-names;
#address-cells = <1>;
#size-cells = <0>;
pinctrl-names = "default";
pinctrl-0 = <&spi2_bus>;
if-spi@0 {
compatible = "if_spi_comp";
reg = <0>;
spi-max-frequency = <12000000>;
spi-cpha;
gpio-controller;
#gpio-cells = <2>;
mif_spi,gpio_ipc_mrdy = <&gpg3 2 0x1>;
mif_spi,gpio_ipc_sub_mrdy = <&gpg3 1 0x1>;
mif_spi,gpio_ipc_srdy = <&gpg3 0 0x1>;
mif_spi,gpio_ipc_sub_srdy = <&gpg3 5 0x0>;
mif_spi,gpio_cp_dump_int = <&gpa3 5 0x0>;
controller-data {
cs-gpio = <&gpd5 1 0 0 3>;
samsung,spi-feedback-delay = <0>;
};
};
};
modem驱动还包括以下几个文件,创建相关的节点并通过spi接口实现通信。
1.以下两个文件指定要生成的节点,以及每个节点的通信类型等等,并进行初始化
board-sprd6500-modems.c
sipc4_mode.c
1)在board-sprd6500-modem.c文件中定义了以下很多需要生成的节点的名字和通信接口类型,还需要读取下面的device tree的内容初始化一些GPIO端口等。
static struct modem_io_t gsm_io_devices[] = {
[0] = {
.name = "gsm_boot0",
.id = 0x1,
.format = IPC_BOOT,
.io_type = IODEV_MISC,
.links = LINKTYPE(LINKDEV_SPI),
},
[1] = {
.name = "gsm_ipc0",
.id = 0x01,
.format = IPC_FMT,
.io_type = IODEV_MISC,
.links = LINKTYPE(LINKDEV_SPI),
},
...
[4] = {
.name = "gsm_rmnet0",
.id = 0x2A,
.format = IPC_RAW,
.io_type = IODEV_NET,
.links = LINKTYPE(LINKDEV_SPI),
},
[5] = {
.name = "gsm_rmnet1",
.id = 0x2B,
.format = IPC_RAW,
.io_type = IODEV_NET,
.links = LINKTYPE(LINKDEV_SPI),
},
...
}
sprd6500 {
compatible = "if_sprd6500_comp";
mif_cp2,phone_on-gpio = <&gpg3 6 0x1>; /*GSM_PHONE_ON*/
mif_cp2,pda_active-gpio = <&gpg3 4 0x1>; /*HOST_ACTIVE*/
mif_cp2,phone_active-gpio = <&gpg3 3 0x0>; /*SLAVE_ACTIVE*/
mif_cp2,ap_cp_int1-gpio = <&gpj1 2 0x1>;
mif_cp2,ap_cp_int2-gpio = <&gpg2 0 0x1>;
mif_cp2,uart_sel-gpio = <&gpc0 0 0x1> ;
/*mif_cp2,sim_sel-gpio = <0>;*/
mif_cp2,gpio_cp_reset = <&gpc0 5 0x0>;
mif_cp2,uart_txd-gpio = <&gpz1 0 0x2>;
mif_cp2,uart_rxd-gpio = <&gpz1 1 0x2>;
mif_cp2,uart_cts-gpio = <&gpz1 2 0x2>;
mif_cp2,uart_rts-gpio = <&gpz1 3 0x2>;
};
2)sipc4_mode.c文件中的modem_probe()会调用call_link_init_func()函数会根据spi或者hsic等通信方式,调用不同的初始化函数。modem_variantion.h和modem_variation.c文件中用
#define LINK_INIT_CALL(type) type ## _create_link_device
来根据不同的通信方式调用不同的初始化函数。这里是spi接口,所以就会调用spi_create_link_device()函数。这个函数中初始化了link_device数据结构,后面发送数据用的send函数等,都是这里赋值的。
p_spild这个spi_link_device全局变量也是这里初始化的。
在spi_create_link_device()函数中初始化的spi_link_device有很多重要的数据成员。
//发送接收用的buff和sync_buff申请!!
spild->buff = kzalloc(SPI_MAX_PACKET_SIZE, GFP_KERNEL);
spild->sync_buff = kzalloc(SPI_MAX_PACKET_SIZE, GFP_KERNEL);
//以下是几个skb_buff_head
spild->skb_txq[IPC_FMT] = &ld->sk_fmt_tx_q;
spild->skb_txq[IPC_RAW] = &ld->sk_raw_tx_q;
spild->skb_txq[IPC_RFS] = &ld->sk_rfs_tx_q;
//link_device也是保存在spi_link_device数据结构中
ld = &spild->ld;
2.sipc4_io_device.c 这个文件最重要的内容就是根据gsm_io_devices中的端口设置,生成相应的设备并配置相应的文件操作了。下面看一下sipc4_init_io_device()函数中的内容。
1)以下函数中,IODEV_MISC类型很简单,注册misc设备赋值fops。这样相应的端口有操作的时候都会调用misc_io_fops中的函数了。比如gsm_ipc0中写东西,就会调用misc_write()等。
2)IODEV_NET类型的是怎么通过SPI实现net这种类型的通信的?后面再说,,
int sipc4_init_io_device(struct io_device *iod)
{
...
switch (iod->io_typ) {
case IODEV_MISC:
init_waitqueue_head(&iod->wq);
skb_queue_head_init(&iod->sk_rx_q);
INIT_DELAYED_WORK(&iod->rx_work, rx_iodev_work);
iod->miscdev.minor = MISC_DYNAMIC_MINOR;
iod->miscdev.name = iod->name;
iod->miscdev.fops = &misc_io_fops;
ret = misc_register(&iod->miscdev);
break;
case IODEV_NET:
INIT_DELAYED_WORK(&iod->rx_work, rx_iodev_work);
/*TODO: register ether device for NCN device*/
skb_queue_head_init(&iod->sk_rx_q);
INIT_LIST_HEAD(&iod->node_ndev);
#ifdef CONFIG_LINK_ETHERNET
iod->ndev = alloc_etherdev(0);
if (!iod->ndev) {
mif_err("failed to alloc netdev\n");
return -ENOMEM;
}
iod->ndev->netdev_ops = &vnet_ether_ops;
iod->ndev->watchdog_timeo = 5 * HZ;
strcpy(iod->ndev->name, "rmnet%d");
#else
if (iod->use_handover)
iod->ndev = alloc_netdev(0, iod->name,
vnet_setup_ether);
else
iod->ndev = alloc_netdev(0, iod->name, vnet_setup);
if (!iod->ndev) {
mif_err("failed to alloc netdev\n");
return -ENOMEM;
}
#endif
/*register_netdev parsing % */
ret = register_netdev(iod->ndev);
if (ret)
free_netdev(iod->ndev);
mif_debug("(iod:0x%p)\n", iod);
vnet = netdev_priv(iod->ndev);
mif_debug("(vnet:0x%p)\n", vnet);
vnet->iod = iod;
break;
case IODEV_DUMMY:
skb_queue_head_init(&iod->sk_rx_q);
INIT_DELAYED_WORK(&iod->rx_work, rx_iodev_work);
iod->miscdev.minor = MISC_DYNAMIC_MINOR;
iod->miscdev.name = iod->name;
iod->miscdev.fops = &misc_io_fops;
ret = misc_register(&iod->miscdev);
break;
default:
mif_err("wrong io_type : %d\n", iod->io_typ);
return -EINVAL;
}
return ret;
}
疑问:IODEV_NET类型怎么通过spi通道实现的??
那下面可以看一下用户程序比如RIL发送接收数据的时候,内容是怎么组织起来然后一步一步发到spi驱动那里,然后通过寄存器发出去了。顺便也可以看一下spi驱动是怎么利用DMA的^^
sipc4_io_device.c文件中的misc_write()函数就是接受上层发来的所有内容并通过spi发送给modem的函数。
static ssize_t misc_write(struct file *filp, const char __user *buf,
size_t count, loff_t *ppos)
{
struct io_device *iod = (struct io_device *)filp->private_data;
struct link_device *ld = get_current_link(iod);
int frame_len = 0;
char frame_header_buf[sizeof(struct raw_hdr)];
struct sk_buff *skb;
int err;
size_t tx_size;
/* TODO - check here flow control for only raw data */
//计算要发送的整个大小。前面和后面加0x7E,大小以及count!!
//再根据format的类型,计算发送的字符串头的大小。
//IPC_FMT对应的头的数据结构是:fmt_hdr
//IPC_RAW则是raw_hdr等,这个当然是根据modem的类型有所不同~
frame_len = SIZE_OF_HDLC_START +
get_header_size(iod) +
count +
SIZE_OF_HDLC_END;
if (ld->aligned)
frame_len += MAX_LINK_PADDING_SIZE;
//根据frame_len分配一个skb_buffer。这种socket buffer很长用。
//在这里用的意图也很明显,就是为了把发过来的消息加到skb链表之后马上返回。
//之后这些消息发送的任务就交给内核线程。
//因为你不可能让用户代码一直等在那里知道都发送完才返回。谁这么写,你抽他丫的?
skb = alloc_skb(frame_len, GFP_KERNEL);
if (!skb) {
mif_err("fail alloc skb (%d)\n", __LINE__);
return -ENOMEM;
}
switch (iod->format) {
case IPC_BOOT:
case IPC_RAMDUMP:
if (copy_from_user(skb_put(skb, count), buf, count) != 0) {
dev_kfree_skb_any(skb);
return -EFAULT;
}
break;
case IPC_RFS:
memcpy(skb_put(skb, SIZE_OF_HDLC_START), hdlc_start,
SIZE_OF_HDLC_START);
if (copy_from_user(skb_put(skb, count), buf, count) != 0) {
dev_kfree_skb_any(skb);
return -EFAULT;
}
memcpy(skb_put(skb, SIZE_OF_HDLC_END), hdlc_end,
SIZE_OF_HDLC_END);
break;
default:
//以IPC_RAW为例,下面的内容也再简单不过,在skb_buff中,先把头写(0x7E)进去。
//再把要发送的内容写进去。最后再把结尾写进去~~
memcpy(skb_put(skb, SIZE_OF_HDLC_START), hdlc_start,
SIZE_OF_HDLC_START);
memcpy(skb_put(skb, get_header_size(iod)),
get_header(iod, count, frame_header_buf),
get_header_size(iod));
if (copy_from_user(skb_put(skb, count), buf, count) != 0) {
dev_kfree_skb_any(skb);
return -EFAULT;
}
memcpy(skb_put(skb, SIZE_OF_HDLC_END), hdlc_end,
SIZE_OF_HDLC_END);
break;
}
//要是需要对齐,再加上~~
skb_put(skb, calc_padding_size(iod, ld, skb->len));
/* send data with sk_buff, link device will put sk_buff * into the specific sk_buff_q and run work-q to send data */
tx_size = skb->len;//保存需要发送的大小,后面检查是否发送完毕用的
skbpriv(skb)->iod = iod;//iod和ld都保存的skb数据结构里边,,
skbpriv(skb)->ld = ld;
//好了,,发送消息,这里的ld->send()函数下面再介绍
err = ld->send(ld, iod, skb);
if (err < 0) {//这里表示发送失败~~
dev_kfree_skb_any(skb);
return err;
}
if (err != tx_size)//实际发送的消息和需要发送的不一样~~
mif_err("wrong tx size:%s, fmt=%d cnt=%d, tx=%d, ret=%d",
iod->name, iod->format, count, tx_size, err);
/* Temporaly enable t he RFS log for debugging IPC RX pedding issue */
if (iod->format == IPC_RFS)
mif_info("write rfs size = %d\n", count);
return count;
}
下面看ld->send()函数的处理
static int spi_send
(
struct link_device *ld,
struct io_device *iod,
struct sk_buff *skb
)
{
struct sk_buff_head *txq;
enum dev_format fmt = iod->format;
const u32 cmd_ready = 0x12341234;
const u32 cmd_start = 0x45674567;
int ret;
u32 data;
//以IPC_RAW为例,这个函数就把skb放到p_sqlid->skb_txq[fmt]对应的链表头中
//然后在后面启动workqueue发送消息。
switch (fmt) {
case IPC_FMT:
case IPC_RAW:
case IPC_RFS:
txq = p_spild->skb_txq[fmt];
skb_queue_tail(txq, skb);
ret = skb->len;
break;
case IPC_BOOT:
if (get_user(data, (u32 __user *)skb->data))
return -EFAULT;
if (data == cmd_ready) {
p_spild->ril_send_modem_img = 1;
p_spild->ril_send_cnt = 0;
} else if (data == cmd_start) {
p_spild->ril_send_modem_img = 0;
if (!queue_work(p_spild->spi_modem_wq,
&p_spild->send_modem_w))
pr_err("(%d) already exist w-q\n",
__LINE__);
} else {
if (p_spild->ril_send_modem_img) {
memcpy((void *)(p_spild->p_virtual_buff +
p_spild->ril_send_cnt),
skb->data, skb->len);
p_spild->ril_send_cnt += skb->len;
}
}
ret = skb->len;
dev_kfree_skb_any(skb);
return ret;
default:
pr_err("[LNK/E] <%s:%s> No TXQ for %s\n",
__func__, ld->name, iod->name);
dev_kfree_skb_any(skb);
return 0;
}
//这里就是启动workqueue了~
spi_send_work(SPI_WORK_SEND, SPI_WORK);
return ret;
}
发送用的workqueue函数
static void spi_tx_work(void)
{
struct spi_link_device *spild;
struct io_device *iod;
char *spi_packet_buf;
char *spi_sync_buf;
spild = p_spild;
iod = link_get_iod_with_format(&spild->ld, IPC_FMT);
if (!iod) {
mif_err("no iodevice for modem control\n");
return;
}
if (iod->mc->phone_state != STATE_ONLINE)
return;
/* check SUB SRDY, SRDY state */
if (gpio_get_value(spild->gpio_ipc_sub_srdy) ==
SPI_GPIOLEVEL_HIGH ||
gpio_get_value(spild->gpio_ipc_srdy) ==
SPI_GPIOLEVEL_HIGH) {
spi_start_data_send();
return;
}
// GSCHO
if (get_console_suspended())
return;
if (spild->spi_state == SPI_STATE_END)
return;
/* change state SPI_STATE_IDLE to SPI_STATE_TX_WAIT */
spild->spi_state = SPI_STATE_TX_WAIT;
spild->spi_timer_tx_state = SPI_STATE_TIME_START;
//mrdy端口拉高,告诉modem准备发送数据、
//这些控制端口需要看具体的modem的需求~
gpio_direction_output(spild->gpio_ipc_mrdy, SPI_GPIOLEVEL_HIGH);
/* Start TX timer */
spild->spi_tx_timer.expires = jiffies +
((SPI_TIMER_TX_WAIT_TIME * HZ) / 1000);
add_timer(&spild->spi_tx_timer);
/* check SUBSRDY state */
//这里也是,,在用timer检查sub_srdy端口是否被拉高~ 这个是modem的操作
while (gpio_get_value(spild->gpio_ipc_sub_srdy) ==
SPI_GPIOLEVEL_LOW) {
if (spild->spi_timer_tx_state == SPI_STATE_TIME_OVER) {
pr_err("%s spild->spi_state=[%d]\n",
"[spi_tx_work] == spi Fail to receiving SUBSRDY CONF :",
(int)spild->spi_state);
spild->spi_timer_tx_state = SPI_STATE_TIME_START;
gpio_direction_output(spild->gpio_ipc_mrdy,
SPI_GPIOLEVEL_LOW);
/* change state SPI_STATE_TX_WAIT */
/* to SPI_STATE_IDLE */
spild->spi_state = SPI_STATE_IDLE;
spi_send_work(SPI_WORK_SEND, SPI_WORK);
return;
}
}
/* Stop TX timer */
del_timer(&spild->spi_tx_timer);
//下面这段就是准备发送数据了。这里会把具体的skb的buffer数据拷贝到
//spild->buff也就是spi_packet_buf里边来。
if (spild->spi_state != SPI_STATE_START
&& spild->spi_state != SPI_STATE_END
&& spild->spi_state != SPI_STATE_INIT) {
spi_packet_buf = spild->buff;
spi_sync_buf = spild->sync_buff;
gpio_direction_output(spild->gpio_ipc_sub_mrdy, SPI_GPIOLEVEL_HIGH);
gpio_direction_output(spild->gpio_ipc_mrdy, SPI_GPIOLEVEL_LOW);
/* change state SPI_MAIN_STATE_TX_SENDING */
spild->spi_state = SPI_STATE_TX_SENDING;
memset(spi_packet_buf, 0, SPI_MAX_PACKET_SIZE);
memset(spi_sync_buf, 0, SPI_MAX_PACKET_SIZE);
spi_prepare_tx_packet();//拷贝skb里边的内容给发送的buff,也就是spi_packet_buf
#ifdef USE_SPI_HALF_DUPLEX
if (spi_tx_rx_sync((void *)spi_packet_buf, (void *)NULL,
SPI_MAX_PACKET_SIZE)) {
#else
if (spi_tx_rx_sync((void *)spi_packet_buf, (void *)spi_sync_buf,
SPI_MAX_PACKET_SIZE)) {
#endif
/* TODO: save failed packet */
/* back data to each queue */
pr_err("[SPI] spi_dev_send fail\n");
/* add cp reset when spi sync fail */
if (iod)
iod->modem_state_changed(iod,
STATE_CRASH_RESET);
}
spild->spi_state = SPI_STATE_TX_TERMINATE;
gpio_direction_output(spild->gpio_ipc_sub_mrdy, SPI_GPIOLEVEL_LOW);
#ifdef CONFIG_LINK_DEVICE_SPI_DEBUG //DKLee test tem log
mif_err("Data to CP\n");
spi_print_data(spi_packet_buf, 64);
#endif
#ifdef CONFIG_LINK_DEVICE_SPI_DEBUG
pr_info("[SPI] spi_tx_work : success - spi_state=[%d]\n",
(int)spild->spi_state);
#endif
/* change state SPI_MAIN_STATE_TX_SENDING to SPI_STATE_IDLE */
spild->spi_state = SPI_STATE_IDLE;
spi_start_data_send();
} else
return;
}
下面就是发送用的函数,
int spi_tx_rx_sync(u8 *tx_d, u8 *rx_d, unsigned len)
{
struct spi_transfer t;
struct spi_message msg;
memset(&t, 0, sizeof t);
t.len = len;
t.tx_buf = tx_d;
t.rx_buf = rx_d;
t.cs_change = 1;
//t.bits_per_word = 32;
t.bits_per_word = 8;
// t.speed_hz = 10800000;
#ifdef CONFIG_LINK_DEVICE_SPI_DEBUG
printk("%s : len :- %d\n", __func__, len);
printk("%s : mode :- %d\n", __func__, p_spi->mode);
#endif
//按照上面参数内容,组织spi_transfer和spi_spi_message发给spi接口即可~
spi_message_init(&msg);
spi_message_add_tail(&t, &msg);
return spi_sync(p_spi, &msg);
}