Linux驱动基础:三星平台Linux SPI驱动

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通道实现的??

到这里就可以知道modem驱动中是怎么初始化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);
}

到这里,以modem控制驱动为例,说明了modem控制驱动是怎么把要发送的数据整理完发送给spi驱动的~

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值