精华内容
下载资源
问答
  • Dell 外设驱动

    2019-01-17 10:14:43
    Dell 外设驱动,支持键盘,鼠标,USB等外接设备。。。。。
  • 之前在对linux各子驱动系统的专栏分析中,已经实现了虚拟的外设驱动实现,可帮助想学习设备驱动且没有开发板的童鞋提供学习环境,目前实现的虚拟外设驱动均可在ubuntu16.04/ubuntu18.04上运行验证,本篇文章主要将...

         之前在对linux各子驱动系统的专栏分析中,已经实现了虚拟的外设驱动实现,可帮助想学习设备驱动且没有开发板的童鞋提供学习环境,目前此处实现的虚拟外设驱动均可在ubuntu16.04/ubuntu18.04上运行验证,本篇文章主要将这些虚拟外设驱动的实现整合在一起,方便查阅。

             目前已完成了linux下i2c子系统、spi子系统、tty子系统、uart子系统、input子系统、hwmon子系统、gpio子系统、pwm子系统、led子系统、pinctrl子系统中虚拟设备的开发,可以帮助想要学习linux各子模块且没有开发板的童鞋熟悉各子系统的驱动开发流程。虚拟驱动代码均已上传至gitee上,读者可从下面的文章链接内看到对应的gitee路径。

    Linux虚拟i2c控制器实现---适用于无开发板学习i2c driver

    Linux虚拟spi控制器实现---适用于无开发板学习spi driver

    linux虚拟串口控制器实现---适用于无开发板学习tty driver

    linux虚拟串口控制器实现---适用于无开发板学习uart driver

    linux虚拟input device驱动实现---适用于无开发板学习input driver

    Linux虚拟hwmon device驱动实现---适用于无开发板学习hwmon driver

    Linux虚拟hwmon device驱动实现---适用于无开发板学习hwmon driver(适用于新版hwmon driver 接口)

    linux虚拟gpio控制器实现---适用于无开发板学习gpio driver

    Linux虚拟 led设备驱动与ledtrigger驱动实现---适用于无开发板学习led driver

    linux虚拟pwm设备驱动实现---适用于无开发板学习pwm driver

    Linux虚拟pinctrl dev驱动开发实例---适用于无开发板学习pinctrl 子系统驱动

    linux虚拟regulator device驱动实现---适用于无开发板学习regulator子系统驱动

    LINUX虚拟CLK driver驱动实现---适用于无开发板学习CCF子系统驱动

    LINUX虚拟IRQ CONTROLLER驱动实现---适用于无开发板学习IRQ子系统驱动

    以上即为目前已实现的虚拟外设驱动的内容,后续也会不定期更新。

    展开全文
  • 为您提供BabyOS 外设驱动的框架下载,BabyOS适用于MCU项目,她是一套管理功能模块和外设驱动的框架。对项目而言,缩短开发周期。项目开发时选择适用的功能模块及驱动。直接进入功能代码编写的阶段。对工程师而言,...
  • STM32常用外设驱动程序
  • RK3399外设驱动之I2C驱动 文章目录RK3399外设驱动之I2C驱动Linux-I2C框架I2C重要结构体I2C总线i2c_bus_typeI2C驱动i2c_driverI2C设备i2c_clientI2C设配器i2c_adapter匹配原则i2c_add_driver注册调用顺序of_driver_...

    RK3399外设驱动之I2C驱动

    内核版本 Linux4.4
    平台 rk3399
    作者 nineyole

    ​ 很多从单片机开发的朋友转到Linux开发的时候,刚开始很不习惯,还想着从寄存器的角度来分析整个驱动,然而Linux庞大的架构体系让很多人都望而生畏,本文将仔细从驱动层分析到内核core层,来分析整个驱动开发是如何调用到rk3399的i2c寄存器的,并且举例说明在rk3399 i2c实际工作中遇到的相关问题以及解决方法。

    ​ 我们都知道i2c驱动有4个非常重要的模块I2C总线I2C驱动I2C设备I2C设备器。i2c总线维护着两个非常重要的链表,一个是驱动链表,一个是设备链表,每当注册进一个驱动(或设备),就会将其添加到总线上相应的链表上,然后遍历总线的设备(或驱动)链表的所有设备(或驱动),通过总线的匹配函数判断是够匹配,如果匹配,则调用驱动的probe函数,然后我们就可以在probe函数注册设备,创建设备节点,实现fops等。

    下面我们将以rk3399平台为例来分别介绍这几个模块。

    Linux-I2C框架

    一般来说I2C都是一个主设备,然后带很多个从设备。其示意图如下图所示。
    在这里插入图片描述

    I2C重要结构体

    I2C总线i2c_bus_type

    i2c_bus_type定义如下:

    struct bus_type i2c_bus_type = {
    	.name		= "i2c",
    	.match		= i2c_device_match,
    	.probe		= i2c_device_probe,
    	.remove		= i2c_device_remove,
    	.shutdown	= i2c_device_shutdown,
    };
    

    在i2c_init中会调用下面函数进行注册

    retval = bus_register(&i2c_bus_type);
    

    I2C总线同时对应着/bus下的一条总线,这个i2c_bus_type总线结构体管理着i2c设备与I2C驱动的匹配、探测、移除等操作。当调用i2c_add_driver进行i2c设备注册的时候,就会最终调用drv->bus->match(dev, drv),即i2c_device_match函数,来查看I2C设备和I2C驱动是否匹配,如果匹配就调用status = driver->probe(client, i2c_match_id(driver->id_table, client));调用driver注册时候生命的probe函数。

    可以通过cat查看当前注册了哪些设备。

    rk3399_all:/ # cat sys/bus/i2c/devices/                                      
    0-001b/  0-0041/  1-001a/  2-0018/  4-0022/  4-006b/  i2c-0/   i2c-2/   i2c-5/
    0-0040/  1-0010/  2-0009/  2-0037/  4-0062/  5-0014/  i2c-1/   i2c-4/   i2c-9/
    

    I2C驱动i2c_driver

    我们在注册一个i2c驱动的时候就需要填充这个结构体,并且跟i2c设备进行绑定。

    struct i2c_driver {
        /* Standard driver model interfaces */
        int (*probe)(struct i2c_client *, const struct i2c_device_id *);
        int (*remove)(struct i2c_client *);
        /* driver model interfaces that don't relate to enumeration  */
        void (*shutdown)(struct i2c_client *);
        int (*command)(struct i2c_client *client, unsigned int cmd, void *arg);
        struct device_driver driver;
        const struct i2c_device_id *id_table;
        /* Device detection callback for automatic device creation */
        int (*detect)(struct i2c_client *, struct i2c_board_info *);
        const unsigned short *address_list;
        struct list_head clients;
    };
    

    I2C设备i2c_client

    I2C设备,就是具体的一个设备,比如触摸屏、电量计,电源、陀螺仪等设备,都是由i2c_client结构体来负责维护的。

    struct i2c_client {
    	unsigned short flags;		/* div., see below		*/
    	unsigned short addr;		/* chip address - NOTE: 7bit	*/
    					/* addresses are stored in the	*/
    					/* _LOWER_ 7 bits		*/
    	char name[I2C_NAME_SIZE];//设备名称
    	struct i2c_adapter *adapter;	/* the adapter we sit on	*/
    	struct device dev;		/* the device structure		*/
    	int irq;			/* irq issued by device	*/
    	struct list_head detected;
    };
    

    在我们进入具体的设备的probe函数的时候都会传递一个i2c_client的结构体过来,这个结构体是如何传递过来的呢?比如针对gt9xx的触摸屏驱动:

    static int goodix_ts_probe(struct i2c_client *client, const struct i2c_device_id *id)
    

    i2c_device_probe中有如下代码:而i2c_device_probe中会调用goodix_ts_probe函数,因此这个goodix_ts_probe的i2c_client就来自于下面的代码:

    struct i2c_client	*client = i2c_verify_client(dev);
    

    而这个dev就是和对应dts中配置的i2c设备中的匹配起来的,包括地址,clk等参数。

    I2C设配器i2c_adapter

    用于I2C驱动和I2C设备间的通信,是针对CPU上I2C控制器的一个具体实现。其定义如下:

    struct i2c_adapter {
    	struct module *owner;
    	unsigned int class;		  /* classes to allow probing for */
    	const struct i2c_algorithm *algo; /* the algorithm to access the bus */
    	void *algo_data;
    	/* data fields that are valid for all devices	*/
    	struct rt_mutex bus_lock;
    	int timeout;			/* in jiffies */
    	int retries;
    	struct device dev;		/* the adapter device */
    	int nr;
    	char name[48];
    	struct completion dev_released;
    	struct mutex userspace_clients_lock;
    	struct list_head userspace_clients;
    	struct i2c_bus_recovery_info *bus_recovery_info;
    	const struct i2c_adapter_quirks *quirks;
    };
    

    其中i2c_algorithm是算法的意思,怎么理解呢,就相当于他是用来实现如何通信的。比如i2c的start stop等都是通过i2c_algorithm中的对应来实现的。如果你想自己写cpu的驱动,就只需要去实现这个master_xfer的函数即可。

    struct i2c_algorithm {
    	int (*master_xfer)(struct i2c_adapter *adap, struct i2c_msg *msgs,int num);//主设备时候的发送函数
    	int (*smbus_xfer) (struct i2c_adapter *adap, u16 addr,//从设备时候的发送函数
    			   unsigned short flags, char read_write,
    			   u8 command, int size, union i2c_smbus_data *data);
    	/* To determine what the adapter supports */
    	u32 (*functionality) (struct i2c_adapter *);
    };
    

    匹配原则

    i2c_add_driver注册调用顺序

    i2c_add_driver//注册i2c的driver
    -->i2c_register_driver//调用系统接口
    	-->driver_register(&driver->driver);//调用driver_register
    		-->ret = bus_add_driver(drv);//将driver添加大到bus
    			-->error = driver_attach(drv);
    				-->bus_for_each_dev(drv->bus, NULL, drv, __driver_attach);
    

    其中__driver_attach的定义如下:driver_match_device匹配之后,就会执行driver_probe_device函数。即我们需要使用的probe函数。在这个probe函数中实现注册设备,创建设备节点,实现fops等。

    static int __driver_attach(struct device *dev, void *data)
    {
        struct device_driver *drv = data;
        if (!driver_match_device(drv, dev))/*判定当前是否匹配,如果driver_match_device返回1表示匹配成功。返回0表示匹配失败*/
            return 0;
        if (dev->parent)	/* Needed for USB */
            device_lock(dev->parent);
        device_lock(dev);
        if (!dev->driver)
            driver_probe_device(drv, dev);//执行probe函数
        device_unlock(dev);
        if (dev->parent)/* Needed for USB */
            device_unlock(dev->parent);
        return 0;
    }
    

    driver_match_device函数定义如下:

    static inline int driver_match_device(struct device_driver *drv,
    				      struct device *dev)
    {
      	return drv->bus->match ? drv->bus->match(dev, drv) : 1;//调用总线的macth函数 
    }
    

    上面这个drv->bus->match,就是前面i2c_bus_type所指向的i2c_device_match,其函数定义如下:这里定义了三种匹配方式,在老的Linux内核中采用的是i2c_match_id,而最新的系统都是使用的是of_driver_match_device进行匹配的。

    static int i2c_device_match(struct device *dev, struct device_driver *drv)
    {
        /* Attempt an OF style match */
        if (of_driver_match_device(dev, drv))//of_driver_match_device匹配
            return 1;/*返回1表示匹配成功。返回0表示匹配失败*/
        /* Then ACPI style match */
        if (acpi_driver_match_device(dev, drv))
            return 1;
        driver = to_i2c_driver(drv);
        /* match on an id table if there is one */
        if (driver->id_table)
            return i2c_match_id(driver->id_table, client) != NULL;//调用i2c_match_id进行匹配
        return 0;
    }
    

    of_driver_match_device分析

    在上面的代码中,优先是采用的of_driver_match_device匹配规则,而最后才是i2c_match_id匹配,整个of_driver_match_device调用的函数顺序如下图所示:

    of_driver_match_device
    -->of_match_device
    	-->__of_match_node
    		-->__of_device_is_compatible
    

    __of_device_is_compatible的函数定义如下:Returns 0 表示没有匹配, 一个int数据表示匹配成功。匹配的角度包括compatible && type && name正常来说我们一般匹配的是compatible。

    static int __of_device_is_compatible(const struct device_node *device,
                                         const char *compat, const char *type, const char *name)
    {
        if (compat && compat[0]) {
            prop = __of_find_property(device, "compatible", NULL);//从dts中获取compatible的名字
            for (cp = of_prop_next_string(prop, NULL); cp;
                 cp = of_prop_next_string(prop, cp), index++) {
                if (of_compat_cmp(cp, compat, strlen(compat)) == 0) {
                    score = INT_MAX/2 - (index << 2);
                    break;
                }
            }
        }
        /* Matching type is better than matching name */
        if (type && type[0]) {//对type进行匹配判断
            if (!device->type || of_node_cmp(type, device->type))
                return 0;
            score += 2;
        }
        /* Matching name is a bit better than not */
        if (name && name[0]) {//对name进行匹配判断
            if (!device->name || of_node_cmp(name, device->name))
                return 0;
            score++;
        }
        return score;
    }
    

    到此,通过i2c_add_driver注册之后,然后调用到匹配函数,匹配之后就会执行driver_probe_device,可以参考__device_attach_driver函数。

    probe函数调用逻辑分析

    通过前面分析可以知道,当match通过之后就会调用driver_probe_device函数,其调用逻辑如下:

    driver_probe_device(drv, dev);
    -->ret = really_probe(dev, drv);
    	-->ret = dev->bus->probe(dev);
    		-->status = driver->probe(client, i2c_match_id(driver->id_table, client));//driver注册时候生命的probe函数。
    

    driver_probe_device调用really_probe,然后调用到dev->bus->probe。其中dev->bus->probe就是前面讲的i2c_device_probe

    通过status = driver->probe(client, i2c_match_id(driver->id_table, client));调用driver注册时候生命的probe函数。

    rk3399 i2c驱动代码

    rk3399 i2c驱动从注册到调用

    本章将仔细从驱动层分析到内核core层,来分析整个驱动开发是如何调用到rk3399的i2c寄存器进行读写的。

    i2c-rk3x.c中有如下的rk3x_i2c_driver驱动代码,这个其实就是注册了一个

    static struct platform_driver rk3x_i2c_driver = {
    	.probe   = rk3x_i2c_probe,
    	.remove  = rk3x_i2c_remove,
    	.driver  = {
    		.name  = "rk3x-i2c",
    		.of_match_table = rk3x_i2c_match,
    		.pm = &rk3x_i2c_pm_ops,
    	},
    };
    module_platform_driver(rk3x_i2c_driver);
    

    比如在rk3399.dtsi中有如下的定义:

    i2c1: i2c@ff110000 {
        compatible = "rockchip,rk3399-i2c";
        reg = <0x0 0xff110000 0x0 0x1000>;
        clocks = <&cru SCLK_I2C1>, <&cru PCLK_I2C1>;
        clock-names = "i2c", "pclk";
        interrupts = <GIC_SPI 59 IRQ_TYPE_LEVEL_HIGH 0>;
        pinctrl-names = "default";
        pinctrl-0 = <&i2c1_xfer>;
        #address-cells = <1>;
        #size-cells = <0>;
        status = "disabled";
    };
    

    在rk3x_i2c_probe函数中,注册了rk3x_i2c_irq函数,用来响应start、read、write、stop的。

    ret = devm_request_irq(&pdev->dev, irq, rk3x_i2c_irq,
    		       0, dev_name(&pdev->dev), i2c);
    

    当用户调用了了__i2c_transfer进行数据发送的时候就会调用master_xfer从而调用rk3x_i2c_xfer。

    rk3x_i2c_xfer的函数定义如下:我们把函数进行了删减,只留下核心代码。

    static int rk3x_i2c_xfer(struct i2c_adapter *adap,
                             struct i2c_msg *msgs, int num)
    {
        for (i = 0; i < num; i += ret) {
            ret = rk3x_i2c_setup(i2c, msgs + i, num - i);//调用setup函数,并确定是要发送还是接受
            if (ret < 0) {
                dev_err(i2c->dev, "rk3x_i2c_setup() failed\n");
                break;
            }
            rk3x_i2c_start(i2c);//调用start函数,产生中断。
            spin_unlock_irqrestore(&i2c->lock, flags);
        }
    }
    

    rk3x_i2c_xfer产生REG_INT_START中断之后就会进入rk3x_i2c_probe函数中注册的rk3x_i2c_irq中断,在中断中会对i2c->state进行判定,并执行对应的,start、read、write、stop等。rk3x_i2c_irq定义如下:

    static irqreturn_t rk3x_i2c_irq(int irqno, void *dev_id)
    {
        switch (i2c->state) {
            case STATE_START:
                rk3x_i2c_handle_start(i2c, ipd);//处理start中断
                break;
            case STATE_WRITE:
                rk3x_i2c_handle_write(i2c, ipd);//处理写中断
                break;
            case STATE_READ:
                rk3x_i2c_handle_read(i2c, ipd);//处理读中断
                break;
            case STATE_STOP:
                rk3x_i2c_handle_stop(i2c, ipd);//处理stop中断
                break;
            case STATE_IDLE:
                break;
        }
        return IRQ_HANDLED;
    }
    

    设备驱动read函数调用逻辑关系

    以下是gt9xx触摸屏的调用逻辑关系:

    gtp_i2c_read
    -->i2c_transfer
    	-->ret = __i2c_transfer(adap, msgs, num);
    		-->ret = adap->algo->master_xfer(adap, msgs, num);
    			-->rk3x_i2c_irq
                   -->rk3x_i2c_handle_start
                      -->rk3x_i2c_handle_read
                    	-->val = i2c_readl(i2c, RXBUFFER_BASE + (i / 4) * 4);
    

    在 i2c-rk3x.c中有如下的定义:

    static const struct i2c_algorithm rk3x_i2c_algorithm = {
    	.master_xfer		= rk3x_i2c_xfer,
    	.functionality		= rk3x_i2c_func,
    };
    

    rk3x_i2c_xfer函数中会调用rk3x_i2c_start(i2c)产生一个start 的中断,从而会进入rk3x_i2c_irq中断函数,并且最终调用rk3x_i2c_handle_read函数。

    rk3x_i2c_handle_read的函数定义如下:

    static void rk3x_i2c_handle_read(struct rk3x_i2c *i2c, unsigned int ipd)
    {
    ...
        /* ack interrupt */
        i2c_writel(i2c, REG_INT_MBRF, REG_IPD);//可以参考rk3399 TRM 这里就是清除I2C接收中断
        /* Can only handle a maximum of 32 bytes at a time */
        if (len > 32)//最大只能4*8=32字节
            len = 32;
        /* read the data from receive buffer */
        for (i = 0; i < len; ++i) {
            if (i % 4 == 0)
                val = i2c_readl(i2c, RXBUFFER_BASE + (i / 4) * 4);//#define RXBUFFER_BASE 0x200 从缓冲区读取数据
            byte = (val >> ((i % 4) * 8)) & 0xff;
            i2c->msg->buf[i2c->processed++] = byte;
        }
        /* are we finished? */
        if (i2c->processed == i2c->msg->len)
            rk3x_i2c_stop(i2c, i2c->error);//接收完成,发送stop信号完成当前传送。
        else
            rk3x_i2c_prepare_read(i2c);//清空等待下次继续接收
    }
    

    同时在rk3399的TRM中可以看到RXbuf是从0x0200的偏移地址开始的,因此一个完整的数据读取链条就打通了,从设备调用gtp_i2c_read到i2c_readl(i2c, RXBUFFER_BASE + (i / 4) * 4),并最终到底层地址。可以从中看出,rk3399的i2c最多有8个接收寄存器,每个缓冲区4个字节,因此一次最大只能32字节。
    在这里插入图片描述

    设备驱动write函数调用关系

    一般来说在i2c的驱动中,write函数调用的还是比较少的,主要用来写入配置参数信息等。下面将从驱动层解析到rkk3399底层寄存器的逻辑。

    i2c_write_bytes
    -->gup_i2c_write
    	-->i2c_transfer(client->adapter, &msg, 1);
    		-->ret = __i2c_transfer(adap, msgs, num);
    			-->ret = adap->algo->master_xfer(adap, msgs, num);
    				-->rk3x_i2c_irq
    					-->rk3x_i2c_setup
    

    在rk3x_i2c_setup中有如下的代码:当在msg设置为I2C_M_WR的时候,就会设置i2c->mode = REG_CON_MOD_TX;从而在rk3x_i2c_handle_start中就会去填充需要发送的数据。

    if (msgs[0].flags & I2C_M_RD) {
        addr |= 1; /* set read bit *//*
    		 * We have to transmit the slave addr first. Use
    		 * MOD_REGISTER_TX for that purpose.
    		 */
        i2c->mode = REG_CON_MOD_REGISTER_TX;
        i2c_writel(i2c, addr | REG_MRXADDR_VALID(0),
                   REG_MRXADDR);
        i2c_writel(i2c, 0, REG_MRXRADDR);
    } else {
        i2c->mode = REG_CON_MOD_TX;
    }
    

    填充的函数如下:

    static void rk3x_i2c_fill_transmit_buf(struct rk3x_i2c *i2c)
    {
        ...
        i2c_writel(i2c, val, TXBUFFER_BASE + 4 * i);
        if (i2c->processed == i2c->msg->len)
            break;
        i2c_writel(i2c, cnt, REG_MTXCNT);
    }
    

    可以看到最终调用了i2c_writel(i2c, val, TXBUFFER_BASE + 4 * i);其中TXBUFFER_BASE在rk3399的规格书中定义如下:可以看到一共有9个缓冲区,每个缓冲区有4个字节,因此单次最大发送32字节。
    在这里插入图片描述

    I2C调试方法

    关于I2C tools

    I2C tool 是一个开源工具,需自行下载进行交叉编译,代码下载地址:

    https://www.kernel.org/pub/software/utils/i2c‐tools/

    或者

    git clone git://git.kernel.org/pub/scm/utils/i2c‐tools/i2c‐tools.git
    

    编译后会生成 i2cdetect,i2cdump,i2cset,i2cget 等工具,可以直接在命令行上调试使用:

    i2cdetect – 用来列举 I2C bus 和上面所有的设备

    i2cdump – 显示 i2c 设备所有 register 的值

    i2cget – 读取 i2c 设备某个 register 的值

    i2cset – 写入 i2c 设备某个 register 的值

    设备NACK问题排查

    我们在调试I2C设备的时候,第一步需要保证i2c地址正确,但是如何保证呢,下面是之前在调试的时候通过逻辑分析仪抓取到的波形,可以明显看到发送地址6E之后,返回了NACK,可以明确到设备通信失败了。
    在这里插入图片描述

    如果i2c返回nack,则有两种情况:第一种就是i2c地址错误,如何排查呢?

    上面显示的波形是0x6E的数据,但是对于瑞芯微来说,实际我们需要通过以下方式来探测波形:

    root@rk3288:/ # echo 0 > /dev/i2c_detect    
    [  323.414047] I2c0 slave list:   0x1b
    

    这个主要原因是因为瑞芯微的地址和实际地址有偏差,实际给定的地址是0x6E,而用探测出来的地址是0x37。因此瑞芯微的地址相当于是需要0x6E>>1=0x37.之前调试也碰到同样的问题。我们在探测的时候只需要使用下面的方式就可以探测到对应的地址。

    root@rk3288:/ # echo 1 > /dev/i2c_detect
    [  365.542976] I2c1 slave list:   0x37
    

    修改之后,就可以探测到返回的ACK相关的波形,然后修改了dts的配置,将0x6E改为0x37就可以正常获取到相关的数据点,可以正常读写。

    正常来说,我们还是需要深究一下原因的,跟踪了一下源代码,在 i2c-rk3x.c中的rk3x_i2c_setup有如下的定义,可以看到其将地址左移了一位,因此我们在填写dts的时候需要将i2c实际地址右移一位。

    static int rk3x_i2c_setup(struct rk3x_i2c *i2c, struct i2c_msg *msgs, int num)
    {
    	u32 addr = (msgs[0].addr & 0x7f) << 1;
    }
    

    因此针对调用 I2C 传输接口返回值为 -6(-ENXIO)时候,表示为 NACK 错误,即对方设备无应答响应,这种情况一 般为外设的问题,常见的有以下几种情况:

    1. I2C 地址错误;
    2. I2C slave 设备处于不正常工作状态,比如没有上电,错误的上电时序以及设备异常等;
    3. I2C 时序不符合slave设备所要求也会产生 NACK 信号,比如 slave 设备需要的是 stop 信号,而不是 repeat start 信号的时候;
    4. I2C 总线受外部干扰导致的,用示波器测量可以看到是一个ACK波形。

    i2c通信出现timeout

    以下类容来自rk官方:

    当出现 I2C 的 log:"timeout, ipd: 0x00, state: 1"时,此时 I2C 控制器工作异常,无法产生中断状态,start 时序无法发出,有以下几种可能:

    1. I2C SCL或者SDA Pin 脚iomux错误;
    2. I2C 的上拉电压不对,如电压不够或者上拉电源没有等;
    3. I2C Pin 脚被外设拉住,电压不对;
    4. I2C 时钟未开,或者时钟源太小;
    5. I2C 同时配置了CON_START 和 CON_STOP 位。

    当出现 I2C 的 log:"timeout, ipd: 0x10, state: 1"时,此时 I2C 控制器工作正常,但是 cpu 无法响应 I2C 中 断,此时可能cpu0被阻塞了(一般 I2C 中断都在 cpu0上面,通过cat /proc/interrups 可以查看),或者可能 是 I2C 中断位被关闭了。

    当出现 I2C 的 log 类似:"timeout, ipd: 0x80, state: 1"时,看到 ipd 为 0x80 打印,可以说明当前 SCL 被 slave 拉住,要判断被哪个 slave 拉住:

    一是排除法,适用于外设不多的情况,而且复现概率高;

    二是需要修改硬件,在 SCL 总线上串入电阻,通过电阻两端产生的压差来确定,电压更低的那端外设为拉低的 slave,电阻的选取以不影响 I2C 传输且可以看出压差为标准,一般上拉电阻的1/20 以上都可以,如果是 host 拉低也可以看出。另外在此基础上通过示波器来抓取波形更加直观,比较不同 slave 和 host 的低电平大小,与最后出问题时的低电平大小比较,相等的就是拉低总线的”元凶“。 遇到比较多的情况是sda被拉低,证明是谁拉低的,同样参考上面 “SCL 被拉低" 的方法两种。

    展开全文
  • stm32f407部分外设驱动

    2017-12-07 21:38:14
    stm32f407部分外设驱动,can总线,定时器,gpio,串口,系统时钟等
  • dsPIC33E 内部Flash读写及其它外设驱动,使用芯片为dsPIC33EP256GP506,文章地址:https://blog.csdn.net/u010875635/article/details/84673935
  • 1主机、外设驱动分离的意义 在Linux设备驱动框架的设计中,除了有分层设计实现以外,还有分隔的思想。举一个简单的例子,假设我们要通过SPI总线访问某外设,在这个访问过程中,要通过操作CPU XXX上的SPI控制器的...
    - by 宋宝华(Barry Song)
    1主机、外设驱动分离的意义

    在Linux设备驱动框架的设计中,除了有分层设计实现以外,还有分隔的思想。举一个简单的例子,假设我们要通过SPI总线访问某外设,在这个访问过程中,要通过操作CPU XXX上的SPI控制器的寄存器来达到访问SPI外设YYY的目的,最简单的方法是:

    return_type xxx_write_spi_yyy(...)

    {

    xxx_write_spi_host_ctrl_reg(ctrl);

    xxx_ write_spi_host_data_reg(buf);

    while(!(xxx_spi_host_status_reg()&SPI_DATA_TRANSFER_DONE));

    ...

    }

    如果按照这种方式来设计驱动,结果是对于任何一个SPI外设来讲,它的驱动代码都是CPU相关的。也就是说,当然用在CPU XXX上的时候,它访问XXX的SPI主机控制寄存器,当用在XXX1的时候,它访问XXX1的SPI主机控制寄存器:

    return_type xxx1_write_spi_yyy(...)

    {

    xxx1_write_spi_host_ctrl_reg(ctrl);

    xxx1_ write_spi_host_data_reg(buf);

    while(!(xxx1_spi_host_status_reg()&SPI_DATA_TRANSFER_DONE));

    ...

    }

    这显然是不能接受的,因为这意味着外设YYY用在不同的CPU XXX和XXX1上的时候需要不同的驱动。那么,我们可以用如图12.4的思想对主机控制器驱动和外设驱动进行分离。这样的结构是,外设a、b、c的驱动与主机控制器A、B、C的驱动不相关,主机控制器驱动不关心外设,而外设驱动也不关心主机,外设只是访问核心层的通用的API进行数据传输,主机和外设之间可以进行任意的组合。

    clip_image001

    图12.4 Linux设备驱动的主机、外设驱动分离

    如果我们不进行如图12.4的主机和外设分离,外设a、b、c和主机A、B、C进行组合的时候,需要9个不同的驱动。设想一共有m个主机控制器,n个外设,分离的结果是需要m+n个驱动,不分离则需要m*n个驱动。

    Linux SPI、I2C、USB、ASoC(ALSA SoC)等子系统都典型地利用了这种分离的设计思想,在本章我们先以简单一些的SPI为例,而I2C、USB、ASoC等则在后续章节会进行详细介绍。

    2 Linux SPI主机和设备驱动

    SPI(同步外设接口)是由摩托罗拉公司开发的全双工同步串行总线,其接口由MISO(串行数据输入),MOSI(串行数据输出),SCK(串行移位时钟),SS(从使能信号)四种信号构成,SS决定了唯一的与主设备通信的从设备,主设备通过产生移位时钟来发起通讯。通讯时,数据由MOSI输出,MISO输入,数据在时钟的上升或下降沿由MOSI输出,在紧接着的下降或上升沿由MISO读入,这样经过8/16次时钟的改变,完成8/16位数据的传输。

    SPI模块为了和外设进行数据交换,根据外设工作要求,其输出串行同步时钟极性(CPOL)和相位(CPHA)可以进行配置。如果 CPOL=0,串行同步时钟的空闲状态为低电平;如果CPOL=1,串行同步时钟的空闲状态为高电平。如果CPHA=0,在串行同步时钟的第一个跳变沿(上升或下降)数据被采样;如果CPHA=1,在串行同步时钟的第二个跳变沿(上升或下降)数据被采样。SPI接口时序如图12.5所示。

    clip_image003

    图12.5 SPI总线时序

    在Linux中,用代码清单12.12的spi_master结构体来描述一个SPI主机控制器驱动,其主要成员是主机控制器的序号(系统中可能存在多个SPI主机控制器)、片选数量、SPI模式和时钟设置用到的函数、数据传输用到的函数等。

    代码清单12.12 spi_master结构体

    1 struct spi_master {

    2 struct device dev;

    3 s16 bus_num;

    4 u16 num_chipselect;

    5

    6 /* 设置模式和时钟 */

    7 int (*setup)(struct spi_device *spi);

    8

    9 /* 双向数据传输 */

    10 int (*transfer)(struct spi_device *spi,

    11 struct spi_message *mesg);

    12

    13 void (*cleanup)(struct spi_device *spi);

    14 };

    分配、注册和注销SPI主机的API由SPI核心提供:

    struct spi_master * spi_alloc_master(struct device *host, unsigned size);

    int spi_register_master(struct spi_master *master);

    void spi_unregister_master(struct spi_master *master);

    在Linux中,用代码清单12.13的spi_driver结构体来描述一个SPI外设驱动,可以认为是spi_master的client驱动。

    代码清单12.13 spi_driver结构体

    1 struct spi_driver {

    2 int (*probe)(struct spi_device *spi);

    3 int (*remove)(struct spi_device *spi);

    4 void (*shutdown)(struct spi_device *spi);

    5 int (*suspend)(struct spi_device *spi, pm_message_t mesg);

    6 int (*resume)(struct spi_device *spi);

    7 struct device_driver driver;

    8 };

    可以看出,spi_driver结构体和platform_driver结构体有极大的相似性,都有probe()、remove()、suspend()、resume()这样的接口。是的,这几乎是一切client驱动的习惯模板。

    在SPI外设驱动中,当透过SPI总线进行数据传输的时候,使用了一套与CPU无关的统一的接口。这套接口的第1个关键数据结构就是spi_transfer,它用于描述SPI传输,如代码清单12.14。

    代码清单12.14 spi_transfer结构体

    1 struct spi_transfer {

    2 const void *tx_buf;

    3 void *rx_buf;

    4 unsigned len;

    5

    6 dma_addr_t tx_dma;

    7 dma_addr_t rx_dma;

    8

    9 unsigned cs_change:1;

    10 u8 bits_per_word;

    11 u16 delay_usecs;

    12 u32 speed_hz;

    13

    14 struct list_head transfer_list;

    15 };

    而一次完整的SPI传输流程可能不只包含1次spi_transfer,它可能包含1个或多个spi_transfer,这些spi_transfer最终通过spi_message组织在一起,其定义如代码清单12.15。

    代码清单12.15 spi_message结构体

    1 struct spi_message {

    2 struct list_head transfers;

    3

    4 struct spi_device *spi;

    5

    6 unsigned is_dma_mapped:1;

    7

    8 /* 完成被一个callback报告 */

    9 void (*complete)(void *context);

    10 void *context;

    11 unsigned actual_length;

    12 int status;

    13

    14 struct list_head queue;

    15 void *state;

    16 };

    通过spi_message_init()可以初始化spi_message,而将spi_transfer添加到spi_message队列的方法则是:

    void spi_message_add_tail(struct spi_transfer *t, struct spi_message *m);

    发起一次spi_message的传输有同步和异步两种方式,使用同步API时,会阻塞等待这个消息被处理完。同步操作时使用的API是:

    int spi_sync(struct spi_device *spi, struct spi_message *message);

    使用异步API时,不会阻塞等待这个消息被处理完,但是可以在spi_message的complete字段挂接一个回调函数,当消息被处理完成后,该函数会被调用。异步操作时使用的API是:

    int spi_async(struct spi_device *spi, struct spi_message *message);

    代码清单12.16是非常典型的初始化spi_transfer、spi_message并进行SPI数据传输的例子,同时它们也是SPI核心层的2个通用API,在SPI外设驱动中可以直接调用它们进行写和读操作。

    代码清单12.16 SPI传输实例spi_write()、spi_read() API

    1 static inline int

    2 spi_write(struct spi_device *spi, const u8 *buf, size_t len)

    3 {

    4 struct spi_transfer t = {

    5 .tx_buf = buf,

    6 .len = len,

    7 };

    8 struct spi_message m;

    9

    10 spi_message_init(&m);

    11 spi_message_add_tail(&t, &m);

    12 return spi_sync(spi, &m);

    13 }

    14

    15 static inline int

    16 spi_read(struct spi_device *spi, u8 *buf, size_t len)

    17 {

    18 struct spi_transfer t = {

    19 .rx_buf = buf,

    20 .len = len,

    21 };

    22 struct spi_message m;

    23

    24 spi_message_init(&m);

    25 spi_message_add_tail(&t, &m);

    26 return spi_sync(spi, &m);

    27 }

    LDD6410开发板所使用的S3C6410的SPI主机控制器驱动位于drivers/spi/spi_s3c.h和drivers/spi/spi_s3c.c这2个文件,其主体是实现了spi_master的setup()、transfer()等成员函数。

    SPI外设驱动遍布于内核的drivers、sound的各个子目录之下,SPI只是一种总线,spi_driver的作用只是将SPI外设挂接在该总线上,因此在spi_driver的probe()成员函数中,将注册SPI外设本身所属设备驱动的类型。

    和platform_driver对应着一个platform_device一样,spi_driver也对应着一个spi_device;platform_device需要在BSP的板文件中添加板信息数据,而spi_device也同样需要。spi_device的板信息用spi_board_info结构体描述,该结构体记录SPI外设使用的主机控制器序号、片选序号、数据比特率、SPI传输模式(即CPOL、CPHA)等。如诺基亚770上2个SPI设备的板信息数据如代码清单12.17,位于板文件arch/arm/mach-omap1/board-nokia770.c。

    代码清单12.17诺基亚770板文件中的spi_board_info

    1 static struct spi_board_info nokia770_spi_board_info[] __initdata = {

    2 [0] = {

    3 .modalias = "lcd_mipid",

    4 .bus_num = 2, /* 用到的SPI主机控制器序号 */

    5 .chip_select = 3, /* 使用哪一号片选 */

    6 .max_speed_hz = 12000000, /* SPI数据传输比特率 */

    7 .platform_data = &nokia770_mipid_platform_data,

    8 },

    9 [1] = {

    10 .modalias = "ads7846",

    11 .bus_num = 2,

    12 .chip_select = 0,

    13 .max_speed_hz = 2500000,

    14 .irq = OMAP_GPIO_IRQ(15),

    15 .platform_data = &nokia770_ads7846_platform_data,

    16 },

    17 };

    在Linux启动过程中,在机器的init_machine()函数中,会通过如下语句注册这些spi_board_info:

    spi_register_board_info(nokia770_spi_board_info,

    ARRAY_SIZE(nokia770_spi_board_info));

    这一点和启动时通过platform_add_devices()添加platform_device非常相似。



     本文转自 21cnbao 51CTO博客,原文链接:http://blog.51cto.com/21cnbao/333312,如需转载请自行联系原作者




    展开全文
  • openwrt笔记6 外设驱动

    2019-10-09 15:20:18
    openwrt笔记6 外设驱动 gpio MT7620a 结合mt7620的datasheet里GPIO pin share schemes以及在mt7620n.dtsi里我们看到有,将GPIO#0到GPIO#72(中间有仅仅做GPO或GPI的)分为四组GPIO0-GPIO3; 对应GPIO0是从GPIO#0...

    openwrt笔记6 外设驱动

    gpio

    MT7620a

    结合mt7620的datasheet里GPIO pin share schemes以及在mt7620n.dtsi里我们看到有,将GPIO#0到GPIO#72(中间有仅仅做GPO或GPI的)分为四组GPIO0-GPIO3;
    
    对应GPIO0是从GPIO#0开始到GPIO#23,一共有24个;
    对应GPIO1是从GPIO#24开始到GPIO#39,一共有16个;
    对应GPIO2是从GPIO#40开始到GPIO#71,一共有32个;
    对应GPIO3对应的是GPIO#72,仅有一个。
    

    控制台操作IO
    终端进入/sys/class/gpio/目录,导出想要控制的GPIO,比如导出GPIO14,则输入以下命令:

    # cd /sys/class/gpio/
    # echo 14 > export
    # ls
    

    此时是不是发现多了一个gpio14的文件夹?如果没有的话,检查一下上述步骤是不是漏掉了什么。

    设置相应IO方向
    进入相应文件夹,可能是以下样子的:

    root@Widora:/sys/devices/10000000.palmbus/10000600.gpio/gpio/gpio14# ls
    active_low  device      direction   edge        subsystem   uevent      value
    

    设置GPIO方向,支持in和out,比如将gpio14设置为输出:

    # echo out > direction''
    

    输出高低电平
    设置为高电平

    # echo 1 > value
    

    设置为低电平

    # echo 0 > value
    

    输入测试
    设置为输入:

    root@Widora:/sys/devices/10000000.palmbus/10000600.gpio/gpio/gpio14# echo in > direction
    

    接下来使用杜邦线段路GPIO14和GND后,读取电平:

    root@Widora:/sys/devices/10000000.palmbus/10000600.gpio/gpio/gpio14# cat value
    0
    

    接下来使用杜邦线段路GPIO14和3V3后,读取电平:

    root@Widora:/sys/devices/10000000.palmbus/10000600.gpio/gpio/gpio14# cat value
    1
    

    额外注意
    目前手中的7688手册是1.4版本,该版本对于GPIO的描述有误导,这些地方的GPO,实测都是GPIO。:

    gpio 目录下默认属性文件用途
    文件名  路径  作用
    export  /sys/class/gpio/export  导出 GPIO
    unexport  /sys/class/gpio/unexport  将导出的 GPIO 从 sysfs 中清除
    gpiochipN
    /sys/class/gpio/gpiochipN/base  设备所管理的 GPIO 初始编号
    /sys/class/gpio/gpiochipN/label  设备信息
    /sys/class/gpio/gpiochipN/ngpio  设备所管理的 GPIO 总数
    /sys/class/gpio/gpiochipN/power  设备供电方面的相关信息
    /sys/class/gpio/gpiochipN/subsystem  符号链接,指向父目录
    /sys/class/gpio/gpiochipN/uevent  内核与 udev(自动设备发现程序)之间的通信接口
    向 export 文件写入需要操作的 GPIO 排列序号 N,就可以导出对应的 GPIO 设备目录。
    操作命令如下:
    # echo N > /sys/class/gpio/export
    例如,导出序号为 68 的 GPIO 的操作接口,在 Shell 下,可以用如下命令:
    # echo 68 > /sys/class/gpio/export
    通过以上操作后在/sys/class/gpio 目录下生成 gpioN 目录,通过读写该设备目录下的属
    广州致远电子股份有限公司(www.zlg.cn)/广州周立功单片机科技有限公司(www.zlgmcu.com)
    381
    性文件(位于 gpioN 下)就可以操作这个 GPIO 的输入和输出。以此类推可以导出其它 GPIO
    

    修改DTS

    修改设备树是openwrt比较难的一部分
    修改设备树需要根据设备的规格书来进行修改,一般设备的datasheet会告诉
    你哪些脚位是有什么复用功能

    golang库

    目前因为sqlite和mysql使用起来比较麻烦,所以使用levelDB作为持久化存储方针,
    另外操作gpio有一个很好的库,不需要每次都export io口,只需要运行就会自动导出

    openwrt patch的使用

    openwrt 32M reboot失败

    我买的MT7688模块,千辛万苦编译好了一个lede17.01版本烧录进去了,然后写程序用到重启
    发现reboot之后一直卡死;然后找度娘发现是以下原因:

    OpenWrt的最新kernel(3.14.28)已经能够支持32M SPI Flash的读写以及擦除操作.
    然而,可能是系统考虑不周,亦或是MT7620系统的BUG,在配置了W25Q256的MT7620开发板系统上,
    无法soft reset!经过查阅相关资料,发现,MT7620默认支持24bit(3byte)的spi地址模式,
    而要支持32M以上的spi flash,则必须切换到32bit(4byte)地址模式.在soft reset的时候,
    spi停留在了32bit模式,没有切换回默认的24bit模式,导致reset后,MT7620在默认的24bit模式,
    无法和32bit模式的spi通讯,系统死机.那么问题来了:如何在soft reset时刻,
    让spi flash切换回24bit模式呢?本文通过设备驱动中的一个shutdown方法来解决这个问题.
    

    然后楼主也提供解决方案了,
    首先,清除干净你编译的内核先:

    make target/linux/{clean,prepare} V=s QUILT=1
    

    然后就可以开始修改内核了,
    完整路径如下:

    ~openwrt/build_dir/target-mipsel_24kc_musl-1.1.16/linux-rampis_mt7688/linux-4.4.71/drivers/mtd/devices
    然后修改m25p80.c文件

    static int m25p_remove(struct spi_device *spi)
    {
        struct m25p *flash = spi_get_drvdata(spi);
    
        // manfeel note: add spi flash reset code
        flash->command[0] = 0x66;
        spi_write(flash->spi, flash->command, 1);
        flash->command[0] = 0x99;
        spi_write(flash->spi, flash->command, 1);
        /* Clean up MTD stuff. */
      //  return mtd_device_unregister(&flash->mtd);  旧版本
      return mtd_device_unregister(&flash->mtd); 新版本内核
    }
    static struct spi_driver m25p80_driver = {
        .driver = {
            .name   = "m25p80",
            .owner  = THIS_MODULE,
        },
        .id_table   = m25p_ids,
        .probe  = m25p_probe,
        .remove = m25p_remove,
        // manfeel, add shutdown method to reset spi flash
        .shutdown = m25p_remove,
    
        /* REVISIT: many of these chips have deep power-down modes, which
         * should clearly be entered on suspend() to minimize power use.
         * And also when they're otherwise idle...
         */
    };
    
    展开全文
  • 以下是关于如何配置SHT3X外设驱动的例程: 1、前提要求 - 熟练使用 ENV 工具 - 熟悉 Kconfig 语法 - 熟悉 STM32CubeMX 工具 - 对 RT-Thread 设备驱动框架有一定了解 2、如何添加更多的外设驱动选项 2、如果是...
  • DSP28335外设驱动

    2020-12-22 22:01:38
    DSP28335外设源码
  • APM飞控添加新的外设驱动,原文链接:http://www.nufeichuiyun.com/?p=300
  • 包含EFM32各个系列的外设驱动示例代码。 参考链接:https://github.com/SiliconLabs/peripheral_examples Supported Series 0 Devices EFM32ZG EFM32HG EFM32TG EFM32G EFM32LG EFM32GG EFM32WG Supported Series 1...
  • 实验四外设驱动程序设计 学习资源中全课中的“hqyj.嵌入式Linux应用程序开发标准教程.pdf”中的第十一章 设备驱动概述: 对设备驱动最通俗的解释就是“驱使硬件设备行动” 。设备驱动与底层硬件直接打交道, 按照...
  • 首先声明文档是自己原创的,通过刻苦阅读linux源代码和翻阅xilinx zynq datasheet写出来的总结文档,对于想了解基于zynq的linux外设驱动的童鞋,有一定帮助,由于是呕心原创,所以分数较高,望见谅
  • 20155223 实验四 外设驱动程序设计 实验任务一 学习资源中全课中的“hqyj.嵌入式Linux应用程序开发标准教程.pdf”中的第十一章 提交康奈尔笔记的照片 提交照片: 实验任务二 在Ubuntu完成资源中全课中的“hqyj....
  • 12.4 主机驱动与外设驱动分离的设计思想12.4.1 主机驱动与外设驱动分离 Linux中SPI、I2C、USB等子系统都利用典型的把主机驱动和外设驱动分离的想法,主机端只负责产生总线上的传输波形,而外设端通过标准的API让...
  • 读写卡模块的外设驱动代码编写技巧 6.4 读写卡模块 > 6.4.1 基本功能 2C 2C 输出数据 2C 和 UART 两种通 信方式其引脚含义详见表 6.19 2C 通信时 需要连接 J1 中的电源引脚和 SCLSDA 引 脚由于 I2C 从机不能主动向 I...
  • 本文档是为需要给现有的 STM32 BSP 添加更多外设驱动的开发者准备的。通过阅读本文,开发者可以按照自己的实际情况给现有 BSP 添加自己需要的驱动。 2. 前提要求 熟练使用 ENV 工具,参考:RT-Thread env 工具用户...
  • BSP 外设驱动使用教程 简介_70) 本文档是为需要在 RT-Thread 操作系统上使用更多开发板资源的开发者准备的。通过使用 ENV 工具对 BSP 进行配置,可以开启更多板载资源,实现更多高级功能。 主要包括以下内容: 如何...
  • 2017-2018-1 20155227 20155318 实验四 外设驱动程序设计 实验目的,实验步骤 实验过程如下。 实验四外设驱动程序设计-1 学习资源中全课中的“hqyj.嵌入式Linux应用程序开发标准教程.pdf”中的第十一章 提交康奈尔...
  • STM32F4外设驱动系列之GPIOGPIO常用相关寄存器GPIO初始化函数与参数GPIO初始化函数:HAL_GPIO_Init()GPIO初始化参数结构体:GPIO_InitTypeDefGPIO初始化实例 GPIO常用相关寄存器 MODER:GPIO端口模式控制寄存器...
  • STM32F4外设驱动系列之IWDGIWDG相关寄存器IWDG初始化函数与参数IWDG相关函数IWDG初始化参数结构体IWDG初始化实例 IWDG相关寄存器 IWDG_KR:关键字寄存器 IWDG_PR:预分频器寄存器 IWDG_RLR:重载寄存器 IWDG_SR:...
  • 2017-2018-1 20155218 20155205 实验四 外设驱动程序设计 实验内容 实验四外设驱动程序设计-1 学习资源中全课中的“hqyj.嵌入式Linux应用程序开发标准教程.pdf”中的第十一章 提交康奈尔笔记的照片(可以多张) ...
  • 实验四 外设驱动程序设计 实验四外设驱动程序设计-1 学习资源中全课中的“hqyj.嵌入式Linux应用程序开发标准教程.pdf”中的第十一章 提交康奈尔笔记的照片(可以多张) 实验四外设驱动程序设计-2 在Ubuntu完成资源...
  • HAL库借鉴面向对象的设计思想,将外设驱动封装为对象。 采用此种开发方式有以下特点: 屏蔽底层硬件:只需了解相关接口函数的功能和参数要求即可 提高开发效率:开发难度较小,开发周期较短,后期的维护升级、以及...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 4,555
精华内容 1,822
关键字:

外设驱动