Linux-spi_代碼分析

代碼目錄

  • 核心層:
    drivers/spi/spi.c 實現SPI核心的功能
  • 總線層
    drivers/spi/spi-rockchip.c
  • 設備層
    所用的spi總線的設備驅動
    drivers/media/spi/rk1608.c

SPI核心層代碼分析

1.SPI子系統註冊函數:spi_init

drivers/spi/spi.c

static int __init spi_init(void)
{
    int status;

    buf = kmalloc(SPI_BUFSIZ, GFP_KERNEL);
    if (!buf) {
        status = -ENOMEM;
        goto err0;
    }
    
    status = bus_register(&spi_bus_type);
    if (status < 0)
        goto err1;
    
    status = class_register(&spi_master_class);
    if (status < 0)
        goto err2;

    if (IS_ENABLED(CONFIG_OF_DYNAMIC))
        WARN_ON(of_reconfig_notifier_register(&spi_of_notifier));
    
    return 0;
}

spi 總線驅動主要結構體
對應的節點:/sys/bus/spi

struct bus_type spi_bus_type = {
    .name       = "spi",    //總線的名字
    .dev_groups = spi_dev_groups,
    .match      = spi_match_device,
    .uevent     = spi_uevent,
}; 

spi平臺(總線層)對應的主要結構體
對應的節點:/sys/class/spi_master

static struct class spi_master_class = {
    .name       = "spi_master",
    .owner      = THIS_MODULE,
    .dev_release    = spi_master_release,
    .dev_groups = spi_master_groups,  //其節點下有對應控制接口
}; 

2.spi_match_device函數分析:

static int spi_match_device(struct device *dev, struct device_driver *drv)
{
    const struct spi_device *spi = to_spi_device(dev); //通過device指針獲取對應的spi_device 指針
    const struct spi_driver *sdrv = to_spi_driver(drv); //通過device_driver指針獲取對應的spi_driver 指針

    /* Attempt an OF style match */
    if (of_driver_match_device(dev, drv))  //如果spi_driver的of_device_id存在,則通過設備節點的compatible來匹配
        return 1;

    /* Then try ACPI */
    if (acpi_driver_match_device(dev, drv))
        return 1;

    if (sdrv->id_table)
        return !!spi_match_id(sdrv->id_table, spi);

    return strcmp(spi->modalias, drv->name) == 0;
}
struct spi_device {
    struct device       dev;   //device 設備
    struct spi_master   *master; //spi設備的更高層描述,每一個spi控制器就對應一個master,一個spi設備必須對應一個master,master下可以有多個spi設備。
    u32         max_speed_hz;
    u8          chip_select;
    u8          bits_per_word;  //如果傳輸是以字節爲單位的話就設置爲8,相應地,如果是以2個字節爲單位則設置爲16。
    u16         mode;
#define SPI_CPHA    0x01            /* clock phase 時鐘極性*/
#define SPI_CPOL    0x02            /* clock polarity 時鐘相位*/
#define SPI_MODE_0  (0|0)           /* (original MicroWire) */   //四種模式,上篇文章有描述
#define SPI_MODE_1  (0|SPI_CPHA)
#define SPI_MODE_2  (SPI_CPOL|0)
#define SPI_MODE_3  (SPI_CPOL|SPI_CPHA)
#define SPI_CS_HIGH 0x04            /* chipselect active high? */
#define SPI_LSB_FIRST   0x08            /* per-word bits-on-wire */
#define SPI_3WIRE   0x10            /* SI/SO signals shared */
#define SPI_LOOP    0x20            /* loopback mode */
#define SPI_NO_CS   0x40            /* 1 dev/bus, no chipselect */
#define SPI_READY   0x80            /* slave pulls low to pause */
#define SPI_TX_DUAL 0x100           /* transmit with 2 wires */
#define SPI_TX_QUAD 0x200           /* transmit with 4 wires */
#define SPI_RX_DUAL 0x400           /* receive with 2 wires */
#define SPI_RX_QUAD 0x800           /* receive with 4 wires */
    int         irq;
    void            *controller_state; //一般設置爲null
    void            *controller_data;   //數據
    char            modalias[SPI_NAME_SIZE]; //,一般來說設備與驅動能否匹配起來就要看它,注意,這裏只是說一般,因爲還有另外兩種匹配的方法,不過大部分設備和驅動能否匹配起來都是根據名字是否相等,當然像USB驅動就不是根據名字來匹配的,而是根據與id table。
    int         cs_gpio;    /* chip select gpio */

    /* the statistics */
    struct spi_statistics   statistics;

    /*
     * likely need more hooks for more protocol options affecting how
     * the controller talks to each chip, like:
     *  - memory packing (12 bit samples into low bits, others zeroed)
     *  - priority
     *  - drop chipselect after each word
     *  - chipselect delays
     *  - ...
     */
};

對於具體的平臺,nand、iic這些都是平臺設備,spi當然也一樣是平臺設備,對於平臺設備,大部分情況下是先註冊設備再註冊驅動。

SPI總線驅動層代碼分析(drivers/spi/spi-rockchip.c)

 spi1: spi@ff1d8000 {
    compatible = "rockchip,px30-spi", "rockchip,rk3066-spi";

1.probe函數分析

static int rockchip_spi_probe(struct platform_device *pdev)
{   
    int ret = 0;
    struct rockchip_spi *rs;
    struct spi_master *master;
    struct resource *mem;
    u32 rsd_nsecs;

    master = spi_alloc_master(&pdev->dev, sizeof(struct rockchip_spi));
    if (!master)
        return -ENOMEM;

    platform_set_drvdata(pdev, master);

    rs = spi_master_get_devdata(master);
    
    /* Get basic io resource and map it */
    mem = platform_get_resource(pdev, IORESOURCE_MEM, 0); //獲取資源
    rs->regs = devm_ioremap_resource(&pdev->dev, mem);  //映射資源
    if (IS_ERR(rs->regs)) {
        ret =  PTR_ERR(rs->regs);
        goto err_ioremap_resource;
    }
    
    rs->apb_pclk = devm_clk_get(&pdev->dev, "apb_pclk");
    if (IS_ERR(rs->apb_pclk)) {
        dev_err(&pdev->dev, "Failed to get apb_pclk\n");
        ret = PTR_ERR(rs->apb_pclk);
        goto err_ioremap_resource;
    }

    rs->spiclk = devm_clk_get(&pdev->dev, "spiclk");
    if (IS_ERR(rs->spiclk)) {
        dev_err(&pdev->dev, "Failed to get spi_pclk\n");
        ret = PTR_ERR(rs->spiclk);
        goto err_ioremap_resource;
    }

    ret = clk_prepare_enable(rs->apb_pclk);  //使能外圍時鐘("apb_pclk" for the peripheral clock.)
    if (ret) {
        dev_err(&pdev->dev, "Failed to enable apb_pclk\n");
        goto err_ioremap_resource;
    }

    ret = clk_prepare_enable(rs->spiclk);//使能輸出時鐘("spiclk" for the transfer-clock)
    if (ret) {
        dev_err(&pdev->dev, "Failed to enable spi_clk\n");
        goto err_spiclk_enable;
    }

    spi_enable_chip(rs, 0);

    rs->type = SSI_MOTO_SPI;
    rs->master = master;
    rs->dev = &pdev->dev;
    rs->max_freq = clk_get_rate(rs->spiclk);

    if (!of_property_read_u32(pdev->dev.of_node, "rx-sample-delay-ns",
                  &rsd_nsecs))
        rs->rsd_nsecs = rsd_nsecs;

    rs->fifo_len = get_fifo_len(rs);
    if (!rs->fifo_len) {
        dev_err(&pdev->dev, "Failed to get fifo length\n");
        ret = -EINVAL;
        goto err_get_fifo_len;
    }

    spin_lock_init(&rs->lock);

    pm_runtime_set_active(&pdev->dev);
    pm_runtime_enable(&pdev->dev);

    master->auto_runtime_pm = true;
    master->bus_num = pdev->id;
    master->mode_bits = SPI_CPOL | SPI_CPHA | SPI_LOOP | SPI_LSB_FIRST; //設置spi模式 時鐘低電平時有效, 數據採樣發生在時鐘(SCK)偶數(2,4,6,...,16)邊沿
    master->num_chipselect = 2;  
    master->dev.of_node = pdev->dev.of_node;
    master->bits_per_word_mask = SPI_BPW_MASK(16) | SPI_BPW_MASK(8);

    master->set_cs = rockchip_spi_set_cs;
    master->prepare_message = rockchip_spi_prepare_message;
    master->unprepare_message = rockchip_spi_unprepare_message;
    master->transfer_one = rockchip_spi_transfer_one; //做slave的時候,需要做此函數設置
    master->handle_err = rockchip_spi_handle_err;

    rs->dma_tx.ch = dma_request_slave_channel(rs->dev, "tx");
    if (IS_ERR_OR_NULL(rs->dma_tx.ch)) {
        /* Check tx to see if we need defer probing driver */
        if (PTR_ERR(rs->dma_tx.ch) == -EPROBE_DEFER) {
            ret = -EPROBE_DEFER;
            goto err_get_fifo_len;
        }
        dev_warn(rs->dev, "Failed to request TX DMA channel\n");
    }

    rs->dma_rx.ch = dma_request_slave_channel(rs->dev, "rx");
    if (!rs->dma_rx.ch) {
        if (rs->dma_tx.ch) {
            dma_release_channel(rs->dma_tx.ch);
            rs->dma_tx.ch = NULL;
        }
        dev_warn(rs->dev, "Failed to request RX DMA channel\n");
    }

    if (rs->dma_tx.ch && rs->dma_rx.ch) {
        rs->dma_tx.addr = (dma_addr_t)(mem->start + ROCKCHIP_SPI_TXDR);
        rs->dma_rx.addr = (dma_addr_t)(mem->start + ROCKCHIP_SPI_RXDR);
        rs->dma_tx.direction = DMA_MEM_TO_DEV;
        rs->dma_rx.direction = DMA_DEV_TO_MEM;

        master->can_dma = rockchip_spi_can_dma;
        master->dma_tx = rs->dma_tx.ch;
        master->dma_rx = rs->dma_rx.ch;
    }

    rs->high_speed_state = pinctrl_lookup_state(rs->dev->pins->p,
                             "high_speed");
    if (IS_ERR_OR_NULL(rs->high_speed_state)) {
        dev_warn(&pdev->dev, "no high_speed pinctrl state\n");
        rs->high_speed_state = NULL;
    }

    ret = devm_spi_register_master(&pdev->dev, master);
    if (ret) {
        dev_err(&pdev->dev, "Failed to register master\n");
        goto err_register_master;
    }

    return 0;
}

SPI設備驅動層代碼分析(drivers/media/spi/rk1608.c)

1.dtsi分析

&spi1 { 引用spi 控制器節點
status = "okay";
max-freq = <48000000>; spi內部工作時鐘
dma-names = "tx","rx"; 使能DMA模式,一般通訊字節少於32字節的不建議用
	spi_rk1608@00 {
		compatible =  "rockchip,rk1608"; 與驅動對應的名字
		reg = <0>; 片選0或者1
		spi-max-frequency = <24000000>; spi clk輸出的時鐘頻率,不超過50M
		spi-cpha; 如果有配,cpha爲1
		spi-cpol; 如果有配,cpol爲1,clk腳保持高電平
		status = "okay"; 使能設備節點
	};
};
spi-max-frequency 是 SPI 的輸出時鐘,是 max-freq 分頻後輸出的,關係是 max-freq >= 2*spimax-frequency。

2.probe函數分析

static struct spi_driver rk1608_driver = {
    .driver = {
        .of_match_table = of_match_ptr(rk1608_of_match),
        .name   = "rk1608",
    },
    .probe      = rk1608_probe,
    .remove     = rk1608_remove,
    .id_table   = rk1608_id,
};
static int rk1608_probe(struct spi_device *spi)
{
	rk1608_parse_dt_property  //解析dts數據
    	reset_gpio   //高->低
        irq_gpio  //必須有
        wakeup //可以沒有,he gpio pin to be used for waking up the controller
        frequency //速率 
        解析dts 中rk1608的配置信息保存到struct rk1608_state *rk1608;
    rk1608_get_remote_node_dev
    	rk1608_get_remote_node_dev
        	of_graph_get_remote_node //找到remote-endpoint節點信息保存到rk1608_state *rk1608
     rk1608_initialize_controls //初始化v4l2_ctrl_handler
     v4l2_spi_subdev_init
     	v4l2_subdev_init
     devm_request_threaded_irq
      	rk1608_threaded_isr,
        	rk1608_msq_recv_msg  // receive a msg from RK1608 -> AP msg queue 第一個參數msg queue,第二個參數a msg pointer buf.寫相應結存器,接受rk1608的信息.
     rk1608_dev_register  //調用到kernel/drivers/media/spi/rk1608_dev.c 
      	pdata->misc.name = "rk_preisp";//名字data->misc.name = "rk_preisp",上層操作打開的節點
         pdata->misc.fops = &rk1608_fops; //操作函數
        	static const struct file_operations rk1608_fops = {
				.owner = THIS_MODULE,
				.open = rk1608_dev_open,  
                	*pdata =container_of(file->private_data, struct rk1608_state, misc);//pdata指向msic的首地址
                    rk1608_set_power(pdata, 1); 
                    	rk1608_power_on(pdata); //設置片選,復位,設置spi速度
                        	rk1608_lsb_w32  //傳輸cmd ,addr,data.
                            rk1608_hw_init   //硬件初始化
                            rk1608_download_fw  //下載rk1608的固件,dts配置的固件名字
                            enable_irq    //使能中斷
                            rk1608_set_log_level
                            	rk1608_send_msg_to_dsp //send a msg to Soc->DSP msg queue
				.release = rk1608_dev_release,
				.write = rk1608_dev_write,
                	copy_from_user //從用戶空間copy
                    
				.poll = rk1608_dev_poll,
				.unlocked_ioctl = rk1608_dev_ioctl,
	#ifdef CONFIG_COMPAT
				.compat_ioctl = rk1608_compat_ioctl,
	#endif
				};
    	misc_register(&pdata->misc); //註冊字符設備
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章