Linux系统SPI驱动总结

平台:rk3288

系统:Android7.1 kernel4.4.143

linux spi 驱动分为三部分:

  • SPI外设驱动:我们写。oled,spi flash 等
  •  linux spi核心层:drivers/spi/spi.c
  • 芯片的SPI控制器驱动:drivers/spi/spi-rockchip.c

Linux SPI 框架理解

1、首先看dts:arch/arm/boot/dts/rk3288.dtsi

spi0: spi@ff110000 {
    compatible = "rockchip,rk3288-spi", "rockchip,rk3066-spi";
    clocks = <&cru SCLK_SPI0>, <&cru PCLK_SPI0>;
    clock-names = "spiclk", "apb_pclk";
    dmas = <&dmac_peri 11>, <&dmac_peri 12>;
    dma-names = "tx", "rx";
    interrupts = ;
    pinctrl-names = "default";
    pinctrl-0 = <&spi0_clk &spi0_tx &spi0_rx &spi0_cs0>;
    reg = <0x0 0xff110000 0x0 0x1000>;
    #address-cells = <1>;
    #size-cells = <0>;
    status = "disabled";
};

2、根据这个属性匹配瑞芯微SPI控制器驱动:drivers/spi/spi-rockchip.c

static int rockchip_spi_probe(struct platform_device *pdev)
{
    ...
    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;
    ...
    return ret;
}

3、调用 devm_spi_register_master 函数注册master 然后进入到 linux SPI的核心层,进入到 drivers/spi/spi.c 文件

drivers/spi/spi.c:
devm_spi_register_master
    /* 注册SPI主控制器 */
    ret = spi_register_master(master);
        /* 从设备树中注册设备 */
        of_register_spi_devices(master);
            /* 遍历可用的子项然后注册到SPI总线上 */
            for_each_available_child_of_node(master->dev.of_node, nc)
                /* 从dts里获取配置信息,reg片选属性:0、1,mode,spi-max-frequency等 */
                spi = of_register_spi_device(master, nc);
                    spi_alloc_device
                    /* 选择设备驱动程序 */
                    of_modalias_node
                            compatible = of_get_property(node, "compatible", &cplen);
                   /*  注册新的设备 */
                   spi_add_device
                        spi_setup(spi);
                        device_add  /* 会绑定到一个spi_driver */

drivers/spi/spi.c 文件里的#if defined(CONFIG_OF)  这个判断成立的函数都是为设备树接口函数去服务的

4、SPI收发

include/linux/spi/spi.h:
spi_write
    spi_message_init(&m);
    初始化一个spi_message      /* 一个不可打断的SPI传输过程: cs=0,传数据,cs=1 */
                                               /* 一个spi_message由多个spi_transfer组成 */
    spi_message_add_tail(&t, &m);      /* spi_transfe是SPI上传输的单方向1个或多个字节 */
    spi_sync(spi, &m);      /* 启动传输并等待完成 */

spi_read
    spi_message_init(&m);
    初始化一个spi_message      /* 一个不可打断的SPI传输过程: cs=0,传数据,cs=1 收数据 */
                                               /* 一个spi_message由多个spi_transfer组成 */
    spi_message_add_tail(&t, &m);      /* spi_transfe是SPI上传输的单方向1个或多个字节 */
    spi_sync(spi, &m);      /* 启动传输并等待完成 */

5、spi_driver如何调用spi_controller

drivers/spi/spi.c:
spi_sync
    __spi_sync(spi, message, 0);
        status = spi_async_locked(spi, message);
            ret = __spi_async(spi, message);
                master->transfer(spi, message);
        /* 等待传输完成 */
        wait_for_completion

不确定,因为看了控制器的传输函数名是master->transfer_one

自己写的DM412驱动,使用SPI控制器的驱动

1、驱动

/*
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 */


#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 

static struct spi_device *g_spidev = NULL;
/*
static void dm412_read(struct spi_device *spi, void *buf, size_t len)
{
	int ret;

	ret = spi_read(spi, buf, len);
	if(ret)
		printk("spi read failed! ret = %d\n",ret);
}
*/
static void dm412_write(struct spi_device *spi, const void *buf, size_t len)
{
	int ret;

	ret = spi_write(spi, buf, len);
	if(ret)
		printk("spi write failed! ret = %d\n",ret);
}

static ssize_t dm412Data_show(struct device *dev, struct device_attribute *attr, char *buf)
{
	return sprintf(buf, "111111\n");		
}

static ssize_t dm412Data_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count)
{
	unsigned long dm412Data;
	int rc = kstrtoul(buf, 0, &dm412Data);
	if (rc)
		return rc;

	if (dm412Data)
		dm412_write(g_spidev,&dm412Data,sizeof(dm412Data));

	return count;
}
static DEVICE_ATTR(dm412Data, 0664, dm412Data_show, dm412Data_store);

static int dm412_probe(struct spi_device *spi)
{
	int ret;
	unsigned char tx_buf[6];
//	int r = 65535;
//	int g = 0;
//	int b = 0;

	printk("%s\n", __func__);

	if (!spi)
		return -ENOMEM;

	g_spidev = spi;
/*
	spi->mode |= SPI_CS_HIGH;
	ret = spi_setup(spi);
	if (ret < 0) {
		dev_err(&spi->dev, "ERR: fail to setup spi\n");
		return -1;
	}
*/
	printk("%s:name=%s,bus_num=%d,cs=%d,mode=%d,speed=%d,bits_per_word=%d\n", __func__, spi->modalias, spi->master->bus_num, 
					spi->chip_select, spi->mode, spi->max_speed_hz, spi->bits_per_word);

	ret = device_create_file(&spi->dev, &dev_attr_dm412Data);
	if (ret)
		printk("dm412Data device_create_file_error\n");
	else
		printk("dm412Data device_create_file_success\n");

	tx_buf[0] = 0xff;
	tx_buf[1] = 0xff;
	tx_buf[2] = 0;
	tx_buf[3] = 0;
	tx_buf[4] = 0;
	tx_buf[5] = 0;

	dm412_write(g_spidev,tx_buf,6);

	return ret;
}

static int dm412_remove(struct spi_device *spi)
{
	printk("%s\n", __func__);
	return 0;
}

static const struct of_device_id dm412_of_match[] = {
	{ .compatible = "siti,dm412", },
	{ /* Sentinel */ },
};
MODULE_DEVICE_TABLE(of, dm412_of_match);

static struct spi_driver dm412_driver = {
	.driver = {
		.name	= "dm412",
		.owner = THIS_MODULE,
		.of_match_table = of_match_ptr(dm412_of_match),
	},
	.probe = dm412_probe,
	.remove = dm412_remove,
};

static int __init dm412_init(void)
{
	return spi_register_driver(&dm412_driver);
}
module_init(dm412_init);

static void __exit dm412_exit(void)
{
	return spi_unregister_driver(&dm412_driver);
}
module_exit(dm412_exit);


MODULE_LICENSE("GPL");
MODULE_AUTHOR("Wu Tao ");
MODULE_DESCRIPTION("dm412 led Driver");

2、DTS配置

&spi0 {
    status = "okay";
    max-freq = <48000000>;
    dm412@00 {
        status = "okay";
        compatible = "siti,dm412";
        reg = <0>;
        spi-max-frequency = <20000000>;
    };
};

一个SPI上有两个 片选引脚   CS0,CS1。这里连接的CS0。所以配置reg为0。如果不太知道dts里面怎么配置,可以查看drivers/spi/spi.c文件里of_register_spi_device函数获取了哪一些属性。还有参考瑞芯微提供的开发文档。

3、驱动最后无法使用,发现 dem412 片选脚是高电平有效。 而rk3288是低电平有效。

使用GPIO口模拟可以正常使用的驱动

1、驱动

/*
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 */


#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 


struct dm412_desc{
	struct gpio_desc *spi_sdi_gpio;
	struct gpio_desc *spi_scl_gpio;
	struct gpio_desc *spi_cs_gpio;
};

struct dm412_desc *g_of_desc = NULL;


static void dm412_spi0_send_cmd(struct dm412_desc *of_desc, unsigned int r, unsigned int g, unsigned int b)
{
	int i;

	gpiod_direction_output(of_desc->spi_cs_gpio, 1);

	for(i = 0; i < 16; i++)
	{
		if(r&0x8000)
			gpiod_direction_output(of_desc->spi_sdi_gpio, 1);
		else
			gpiod_direction_output(of_desc->spi_sdi_gpio, 0);

		gpiod_direction_output(of_desc->spi_scl_gpio, 0);
		udelay(20);
		gpiod_direction_output(of_desc->spi_scl_gpio, 1);	// 时钟上升沿读取数据
		r <<= 1;
		udelay(20);
	}

	for(i = 0; i < 16; i++)
	{
		if(g&0x8000)
			gpiod_direction_output(of_desc->spi_sdi_gpio, 1);
		else
			gpiod_direction_output(of_desc->spi_sdi_gpio, 0);

		gpiod_direction_output(of_desc->spi_scl_gpio, 0);
		udelay(20);
		gpiod_direction_output(of_desc->spi_scl_gpio, 1);	// 时钟上升沿读取数据
		g <<= 1;
		udelay(20);
	}

	for(i = 0; i < 16; i++)
	{
		if(b&0x8000)
			gpiod_direction_output(of_desc->spi_sdi_gpio, 1);
		else
			gpiod_direction_output(of_desc->spi_sdi_gpio, 0);

		gpiod_direction_output(of_desc->spi_scl_gpio, 0);
		udelay(20);
		gpiod_direction_output(of_desc->spi_scl_gpio, 1);	// 时钟上升沿读取数据
		b <<= 1;
		udelay(20);
	}

	for(i = 0; i < 8; i++)   //时钟保持高电平,连发8个脉冲启动自动锁存机制
	{
		gpiod_direction_output(of_desc->spi_sdi_gpio, 0);
		udelay(10);
		gpiod_direction_output(of_desc->spi_sdi_gpio, 1);
		udelay(10);			
	}

	gpiod_direction_output(of_desc->spi_cs_gpio, 0);
	gpiod_direction_output(of_desc->spi_scl_gpio, 0);
	gpiod_direction_output(of_desc->spi_sdi_gpio, 0);

	printk("%s\n", __func__);

}

static ssize_t dm412Data_show(struct device *dev, struct device_attribute *attr, char *buf)
{
	return sprintf(buf, "please input 49bit data:(r<<32|g<<16|b).\n");
}

static ssize_t dm412Data_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count)
{
	unsigned long long dm412Data;
	unsigned int r,g,b;
	int rc = kstrtou64(buf, 0, &dm412Data);		// 字符串转整形
	if (rc)
		return rc;

	r = (dm412Data>>32)&0xffff;
	g = (dm412Data>>16)&0xffff;
	b = dm412Data&0xffff;
	//printk("\n spi0:\n r = %d g = %d b = %d\n ",r,g,b);
	dm412_spi0_send_cmd(g_of_desc, r, g, b);

	return count;
}
static DEVICE_ATTR(dm412Data, 0664, dm412Data_show, dm412Data_store);

static int dm412_probe(struct platform_device *pdev)
{
	int ret;
	int err;
	struct dm412_desc *of_desc;

	printk("%s\n", __func__);


	of_desc = devm_kzalloc(&pdev->dev, sizeof(struct dm412_desc), GFP_KERNEL);
	if (!of_desc)
		return -ENOMEM;

	g_of_desc = of_desc;

	of_desc->spi_scl_gpio = devm_gpiod_get_optional(&pdev->dev, "spi-scl", 0);
	if (IS_ERR(of_desc->spi_scl_gpio)) {
		err = PTR_ERR(of_desc->spi_scl_gpio);
		dev_err(&pdev->dev, "failed to request spi_scl: %d\n", err);
		return err;
	}

	of_desc->spi_cs_gpio = devm_gpiod_get_optional(&pdev->dev, "spi-cs", 0);
	if (IS_ERR(of_desc->spi_cs_gpio)) {
		err = PTR_ERR(of_desc->spi_cs_gpio);
		dev_err(&pdev->dev, "failed to request spi_cs: %d\n", err);
		return err;
	}

	of_desc->spi_sdi_gpio = devm_gpiod_get_optional(&pdev->dev, "spi-sdi", 0);
	if (IS_ERR(of_desc->spi_sdi_gpio)) {
		err = PTR_ERR(of_desc->spi_sdi_gpio);
		dev_err(&pdev->dev, "failed to request spi_sdi: %d\n", err);
		return err;
	}

	gpiod_direction_output(of_desc->spi_cs_gpio, 0);
	gpiod_direction_output(of_desc->spi_sdi_gpio, 0);
	gpiod_direction_output(of_desc->spi_scl_gpio, 0);

	ret = device_create_file(&pdev->dev, &dev_attr_dm412Data);
	if (ret)
		printk("dm412Data device_create_file_error\n");
	else
		printk("dm412Data device_create_file_success\n");

	dm412_spi0_send_cmd(of_desc,168,2556,4690);

	return ret;
}

static int dm412_remove(struct platform_device *pdev)
{
	printk("%s\n", __func__);
	return 0;
}

static const struct of_device_id dm412_of_match[] = {
	{ .compatible = "siti,dm412", },
	{ /* Sentinel */ },
};
MODULE_DEVICE_TABLE(of, dm412_of_match);

static struct platform_driver dm412_driver = {
	.driver = {
		.name	= "dm412",
		.owner = THIS_MODULE,
		.of_match_table = of_match_ptr(dm412_of_match),
	},
	.probe = dm412_probe,
	.remove = dm412_remove,
};

static int __init dm412_init(void)
{
	return platform_driver_register(&dm412_driver);
}
module_init(dm412_init);

static void __exit dm412_exit(void)
{
	return platform_driver_unregister(&dm412_driver);
}
module_exit(dm412_exit);


MODULE_LICENSE("GPL");
MODULE_AUTHOR("Wu Tao ");
MODULE_DESCRIPTION("dm412 led Driver");

创建的文件接口提供给APK使用

2、dts配置

dm412 {
	compatible = "siti,dm412";
	pinctrl-naems = "default";
	pinctrl-0 = <&spi0_init_cmd>;
	spi-scl-gpios = <&gpio5 12 GPIO_ACTIVE_HIGH>;
	spi-cs-gpios  = <&gpio5 13 GPIO_ACTIVE_HIGH>;
	spi-sdi-gpios = <&gpio5 14 GPIO_ACTIVE_HIGH>;
	status = "okay";
};

 

 

 

你可能感兴趣的:(Linux驱动开发)