设备树常用奇奇怪怪速查
设备树里有关节点各种操作,结构体
结构体
struct device_node 保存设备节点的信息
struct of_device_id 结构体
struct property 节点属性的结构体
struct resource 硬件资源结构体 节点里reg属性获取后得到 resource结构体
根据上面的指引到这里找到详细描述
找到设备节点
struct device_node 保存设备节点的信息
struct device_node {const char *name;.//节点中属性为name的值const char *type;//节点中属性为device_type的值phandle phandle;const char *full_name;//节点的名字,在device_node结构体后面放一个字符串,full_name指向它struct fwnode_handle fwnode;struct property *properties;//链表,连接该节点的所有属性struct property *deadprops; /* removed properties */struct device_node *parent;//指向父节点struct device_node *child;// 指向子节点struct device_node *sibling;// 指向兄弟节点
#if defined(CONFIG_OF_KOBJ)struct kobject kobj;
#endifunsigned long _flags;void *data;
#if defined(CONFIG_SPARC)const char *path_component_name;unsigned int unique_id;struct of_irq_controller *irq_trans;
#endif
};
of_device_id结构体
暂时见到过,用这个结构体装着要寻找的设备节点的信息,然后去寻找想要的设备节点
of_find_matching_node_and_match这个函数里面用到过这个结构体
struct of_device_id {char name[32];//节点中属性为name的值char type[32];//节点中属性为device_type的值char compatible[128];// 节点的名字,在device_node结构体后面放一个字符串,full_name指向它const void *data;//链表,连接该节点的所有属性
};
设备树获取节点函数
根据节点路径寻找节点函数
of_find_node_by_path函数 (内核源码/include/linux/of.h)
struct device_node *of_find_node_by_path(const char *path)@description : 寻找节点
@param - const char *path : 指定节点在设备树中的路径。
@return : struct *device_node 结构体指针
@return : NULL失败
根据节点名字寻找节点函数
of_find_node_by_name函数 (内核源码/include/linux/of.h)
struct device_node *of_find_node_by_name(struct device_node *from,const char *name);@description : 名字寻找节点
@param : struct device_node *from : 从指定节点开始查找,NULL从根节点开始查找
@param : const char *name : 要找的节点名字
@return : struct *device_node 结构体指针
@return : NULL 查找失败
根据节点类型寻找节点函数
of_find_node_by_type函数 (内核源码/include/linux/of.h)
struct device_node *of_find_node_by_type(struct device_node *from,const char *type)@param : struct device_node *from : 从指定节点后面开始查找
@param : const char *type : 节点类型,也就是deivce_node->type
@return : struct *device_node
@return : NULL没找到
.根据节点类型和compatible属性寻找节点函数
of_find_compatible_node函数 (内核源码/include/linux/of.h)
struct device_node *of_find_compatible_node(struct device_node *from,const char *type, const char *compatible)@param - struct device_node *from : 从某节点开始查找
@param - const char *type : 查找的类型,也是device_node->type
@param - const char *compatible : compatabile属性,device_node->compatible
根据匹配表寻找节点函数
of_find_matching_node_and_match函数 (内核源码/include/linux/of.h)
static inline struct device_node *of_find_matching_node_and_match(struct device_node *from, const struct of_device_id *matches, const struct of_device_id **match)@description : 结构体包含了更多的匹配参数,也就是说相比前三个寻找节点函数,这个函数匹配的参数更多,对节点的筛选更细。
@param - struct device_node *from : 从哪个节点开始
@param - const struct of_device_id *matches : 和匹配表匹配的节点
@param - const struct of_device_id **match : 一个用来匹配设备节点专用的结构体
@return : struct *device_node : 匹配到的device_node节点
寻找父节点函数
of_get_parent函数 (内核源码/include/linux/of.h)
struct device_node *of_get_parent(const struct device_node *node)@param - const struct device_node *node : 指定谁(节点)要找的父节点
@return : struct * device_noed :device_node节点的指针
@return : NULL没找到
寻找子节点函数
of_get_next_child函数 (内核源码/include/linux/of.h)
struct device_node *of_get_next_child(const struct device_node *node, struct device_node *prev)@param - const struct device_node *node : 指定谁(节点)要找的子节点
@param : struct device_node *prev :前一个子节点。寻找的是prev节点之后的节点。这是一个迭代寻找过程,例如寻找第二个子节点,这里就要填第一个子节点。参数为NULL 表示寻找第一个子节点
@return : struct * device_noed :device_node节点的指针
@return : NULL没找到
从节点里找到想要的属性
刚刚我们从设备树里找到想要的节点,现在就可以从里面拿出想要属性
直接获得属性和值
节点属性的结构体 struct property
struct property {char *name; //属性名int length;//属性长度void *value;//属性值struct property *next; //下一个属性
#if defined(CONFIG_OF_DYNAMIC) || defined(CONFIG_SPARC)unsigned long _flags;
#endif
#if defined(CONFIG_OF_PROMTREE)unsigned int unique_id;
#endif
#if defined(CONFIG_OF_KOBJ)struct bin_attribute attr;
#endif
};
节点里reg属性获取后得到 resource结构体
struct resource {resource_size_t start;//起始地址resource_size_t end;//结束地址const char *name;//属性名字unsigned long flags;unsigned long desc;struct resource *parent, *sibling, *child;
};
从device_node节点里获取属性的函数
提取节点属性函数
of_find_property函数 (内核源码/include/linux/of.h)
struct property *of_find_property(const struct device_node *np,const char *name,int *lenp)@description : 从节点里面提取想要的属性
@param - const struct device_node *np : 指定的设备树节点
@param - const char *name : 属性名的大小
@param - int *lenp : 属性值的大小,作为输出参数
@return :struct *property : 获取到想要的属性值
@return :NULL
读取整型属性函数
相对于上面,数值直接出来,不用再从property结构体里取属性,读取一组
of_property_read_uX_array函数组 (内核源码/include/linux/of.h)
//8位整数读取函数
int of_property_read_u8_array(const struct device_node *np, const char *propname, u8 *out_values, size_t sz)
//16位整数读取函数
int of_property_read_u16_array(const struct device_node *np, const char *propname, u16 *out_values, size_t sz)
//32位整数读取函数
int of_property_read_u32_array(const struct device_node *np, const char *propname, u32 *out_values, size_t sz)
//64位整数读取函数
int of_property_read_u64_array(const struct device_node *np, const char *propname, u64 *out_values, size_t sz)
-------------------------->简化,直接把长度设置为1
//8位整数读取函数
int of_property_read_u8 (const struct device_node *np, const char *propname,u8 *out_values)
//16位整数读取函数
int of_property_read_u16 (const struct device_node *np, const char *propname,u16 *out_values)
//32位整数读取函数
int of_property_read_u32 (const struct device_node *np, const char *propname,u32 *out_values)
//64位整数读取函数
int of_property_read_u64 (const struct device_node *np, const char *propname,u64 *out_values)
int of_property_read_u16 (const struct device_node \*np, const char \*propname,u16 \*out_values)@param - const struct device_node *np : 指定的节点
@param - char *propname :要获取的属性值
@param - u32 *out_values :输出参数,保存获得的返回值
@param - size_t sz 设置想读取的参数长度
@return : 0 : 成功读取
@return : -EINVAL : 属性不存在
@return : -ENODATA :没有要读取的数据
@return : -EOVERFLOW :属性列表太小
读取字符串属性函数
设备节点中存在很多字符串属性,例如compatible、status、type等等,这些属性可以使用查找节点属性函数of_find_property来获取,但是这样比较繁琐。那就用下面这个吧
of_property_read_string函数 (内核源码/include/linux/of.h)
int of_property_read_string(const struct device_node *np,const char *propname,const char **out_string)@param - const struct device_node :设备节点
@param - char *propname : 属性名
@param - char **out_string : 输出函数,获得字符串的指针
@return - 0 :sucess
@return - -EINVAL : 属性不存在
@return - -ENODATA :没有要读取的数据
@return - -EOVERFLOW :属性列表太小
获得属性property后,把地址映射
这时候要用到内存映射相关of函数—>解决之前得到物理地址还要ioremap的转换
内存映射相关of函数
of_iomap函数 (内核源码/drivers/of/address.c)
void __iomem *of_iomap(struct device_node *np, int index)@param - struct device_node *np :要获取的设备节点里的属性的结构体
@param - int index : 通常index有多段,index指定要映射的那一段,标号从0开始
@return : 成功返回转换得到的虚拟地址
@return : 失败返回NULL
常规获取地址of函数
那就是设备树里面设置的实际的物理地址(没有经过映射)
of_address_to_resource函数 (内核源码/drivers/of/address.c)
int of_address_to_resource(struct device_node *dev, int index, struct resource *r)@param - struct device_node *dev : 要获取的设备节点里的属性的结构体
@param - int index : 通常情况下reg属性包含多段,index 用于指定映射那一段,标号从0开始。
@param - struct resource *r : 输出函数-->resource结构体,返回地址得到的信息
@return - 0 :sucess
@return - -EINVAL
@return - -ENODATA
//21年/1月30日更
通过pinctl子系统里GPIO子系统控制引脚
除了上面直接获取属性,通过映射进行操作,还可以通过pinctl子系统里已经有的gpio子系统控制gpio
获取GPIO编号函数of_get_named_gpio
of_get_named_gpio函数(内核源码include/linux/of_gpio.h)
static inline int of_get_named_gpio(struct device_node *np, const char *propname, int index)@param - struct device_node *np : 指定设备节点
@param - const char *propname : GPIO属性名,在设备树和对应属性名对应
@param - int index : 引脚索引值,在设备树中一条引脚属性可以包含多个引脚,该参数用于指定获取那个引脚。
@return - GPIO编号 : sucess
@return - 负数 : failed
GPIO申请函数gpio_request
gpio_request函数(内核源码drivers/gpio/gpiolib-legacy.c)
static inline int gpio_request(unsigned gpio, const char *label);@param - unsigned gpio : 要申请的GPIO编号,上一个函数of_get_named_gpio返回的值
@param - const char *label : 引脚的名字,相当于为申请得到的引脚取了个别名。
@return - 0 : sucess
@return - 负数 : failed
GPIO释放函数
gpio_free函数(内核源码drivers/gpio/gpiolib-legacy.c)
static inline void gpio_free(unsigned gpio);@param - unsigned gpio : 要释放的gpio
@return - NULL
GPIO输出设置函数gpio_direction_output
gpio_direction_output函数(内核源码include/asm-generic/gpio.h)
static inline int gpio_direction_output(unsigned gpio , int value);@param - unsigned gpio : 要设置的GPIO的编号
@param - int value : 输出值,1,表示高电平。0表示低电平(设置的虚拟值与物理值对应,物理值在设备树上)
@return - 0 : suecess
@return - 负数 : failed
GPIO输入设置函数gpio_direction_input
gpio_direction_input函数(内核源码include/asm-generic/gpio.h)
static inline int gpio_direction_input(unsigned gpio)@param - unsigned gpio : 要设置的gpio
@return 0 : sucess
@return 负数 : failed
获取GPIO引脚值函数gpio_get_value
gpio_get_value函数(内核源码include/asm-generic/gpio.h)
static inline int gpio_get_value(unsigned gpio);@param - unsigned gpio : 要获取的gpio编号
@return - gpio状态 : success
@return - 负数 : failed
设置GPIO输出值gpio_set_value
gpio_direction_output函数(内核源码include/asm-generic/gpio.h)
static inline int gpio_direction_output(unsigned gpio, int value);
I2C总线操作
i2c总线包括i2c设备(i2c_client)和i2c驱动(i2c_driver),当我们向linux中注册设备或驱动的时候,按照i2c总线匹配规则进行配对,配对成功,则可以通过i2c_driver中.prob函数创建具体的设备驱动。
设备驱动创建成功,我们还需要实现设备的文件操作接口(file_operations),file_operations中会使用到内核中i2c核心函数(i2c系统已经实现的函数,专门开放给驱动工程师使用)。 使用这些函数会涉及到i2c适配器,也就是i2c控制器。由于ic2控制器有不同的配置,所有linux将每一个i2c控制器抽象成i2c适配器对象。 这个对象中存在一个很重要的成员变量——Algorithm,Algorithm中存在一系列函数指针,这些函数指针指向真正硬件操作代码。
在现代linux中,i2c设备不再需要手动创建,而是使用设备树机制引入,设备树节点是与paltform总线相配合使用的。 所以需先对i2c总线包装一层paltform总线,当设备树节点转换为平台总线设备时,我们在进一步将其转换为i2c设备,注册到i2c总线中。
i2c驱动编写的各种结构体
struct i2c_adapter i2c_适配器
i2c_适配器对应一个i2c控制器,是用于标识物理i2c总线以及访问它所需的访问算法的结构。
sturct i2c_adapter (内核源码/include/linux/i2c.h)
/** i2c_adapter is the structure used to identify a physical i2c bus along* with the access algorithms necessary to access it.*/
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; //struct i2c_algorithm 结构体,访问总线的算法/* 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, struct 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;
};
struct i2c_algorithm i2c算法结构体
直白的说, i2c设备例如mpu6050、i2c接口的oled屏等等就是通过这些函数接口使用i2c总线实现收、发数据的
struct i2c_algorithm (内核源码/include/linux/i2c.h 部分函数)
/* If an adapter algorithm can't do I2C-level access, set master_xferto NULL. If an adapter algorithm can do SMBus access, setsmbus_xfer. If set to NULL, the SMBus protocol is simulatedusing common I2C messages *//* master_xfer should return the number of messages successfullyprocessed, or a negative value on error *///master_xfer: 作为主设备时的发送函数,应该返回成功处理的消息数,或者在出错时返回负值。int (*master_xfer)(struct i2c_adapter *adap, struct i2c_msg *msgs,int num);//smbus_xfer: 作为从设备时的发送函数。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 *);#if IS_ENABLED(CONFIG_I2C_SLAVE)int (*reg_slave)(struct i2c_client *client);int (*unreg_slave)(struct i2c_client *client);
#endif
};
该结构体两个函数定义
.master_xfer = master_xfer,
static int master_xfer(struct i2c_adapter *adapter, struct i2c_msg *msgs, int num)
编写设备驱动时我们会使用“i2c_transfer”函数执行数据的传输,i2c_transfer函数最终就是调用master_xfer函数实现具体的收发工作。
struct i2c_client i2c从设备结构体
struct i2c_client (内核源码/include/linux/i2c.h)
struct i2c_client {//flags: :I2C_CLIENT_TEN表示设备使用10位芯片地址,I2C客户端PEC表示它使用SMBus数据包错误检查unsigned short flags; /* div., see below *///addr: addr在连接到父适配器的I2C总线上使用的地址unsigned short addr; /* chip address - NOTE: 7bit *///name: 表示设备的类型,通常是芯片名char name[I2C_NAME_SIZE];//adapter: struct i2c_adapter 结构体,管理托管这个I2C设备的总线段。struct i2c_adapter *adapter; /* the adapter we sit on *///dev: Driver model设备节点。struct device dev; /* the device structure *///init_irq: 作为从设备时的发送函数。int init_irq; /* irq set at initialization *///irq: 表示该设备生成的中断号。int irq; /* irq issued by device *///detected: struct list_head i2c的成员_驱动程序.客户端列表或i2c核心的用户空间设备列表struct list_head detected;#if IS_ENABLED(CONFIG_I2C_SLAVE)i2c_slave_cb_t slave_cb; /* callback for slave mode */#endif};
struct i2c_msg结构体
描述一个iic消息(内核源码/include/uapi/linux/i2c.h)
struct i2c_msg {__u16 addr; /* slave address */__u16 flags;//消息传输方向和特性。I2C_M_RD:表示读取消息;0:表示发送消息。...__u16 len;//消息数据的长度 /* msg length */__u8 *buf;//符数组存放消息,作为消息的缓冲区 /* pointer to msg data */};
struct i2c_driver i2c设备驱动结构体
struct i2c_driver (内核源码/include/linux/i2c.h)
struct i2c_driver {unsigned int class;int (*probe)(struct i2c_client *, const struct i2c_device_id *);// i2c设备和i2c驱动匹配后,回调该函数指针。int (*remove)(struct i2c_client *);struct device_driver driver;const struct i2c_device_id *id_table;//struct i2c_device_id 要匹配的从设备信息。int (*detect)(struct i2c_client *, struct i2c_board_info *);//设备探测函数const unsigned short *address_list;//设备地址struct list_head clients;//设备链表...};
struct imx_i2c_struct (保存硬件信息)
struct imx_i2c_struct {struct i2c_adapter adapter;struct clk *clk;//时钟频率结构体void __iomem *base;wait_queue_head_t queue;unsigned long i2csr;unsigned int disable_delay;int stopped;unsigned int ifdr; /* IMX_I2C_IFDR */unsigned int cur_clk;unsigned int bitrate;//波特率信息结构体const struct imx_i2c_hwdata *hwdata;struct imx_i2c_dma *dma;//dam相关结构体
};
I2C设备驱动核心函数
i2c_add_adapter() 向linux系统注册一个i2c适配器
注册一个i2c适配器 (内核源码/drivers/i2c/i2c-core-base.c)
//linux系统自动设置i2c适配器编号(adapter->nr)
int i2c_add_adapter(struct i2c_adapter *adapter)
//手动设置i2c适配器编号(adapter->nr)
int i2c_add_numbered_adapter(struct i2c_adapter *adapter)@param - struct i2c_adapter *adapter : 物理控制器对应的适配器
@return - 0 : sucess
@return - 负数 : failed
i2c_add_driver()宏 注册i2c驱动
注册一个i2c驱动(内核源码/include/linux/i2c.h)
#define i2c_add_driver(driver)宏函数的本质是调用了i2c_register_driver()函数
如下
i2c_register_driver() 注册I2C驱动函数
注册一个i2c驱动(内核源码/drivers/i2c/i2c-core-base.c)
int i2c_register_driver(struct module *owner, struct i2c_driver *driver)@param - struct module *owner : 一般为 THIS_MODULE
@param - i2c_driver *driver : 要注册的 i2c_driver.
@return - 0 : sucess
@return - 负数 :失败
i2c_transfer() 数据传输函数
i2c_transfer()函数最终就是调用我们前面讲到的i2c_imx_xfer()函数来实现数据传输。
收发i2c消息(内核源码/drivers/i2c/i2c-core-base.c)
int i2c_transfer(struct i2c_adapter *adap, struct i2c_msg *msgs, int num)@param - struct i2c_adapter *adap : 收发消息所使用的i2c适配器,i2c_client 会保存其对应的 i2c_adapter
@param - struct i2c_msg *msgs :i2c要发送的一个或多个消息
@param - int num : 消息数量,也就是msgs的数量
@return - msgs的数量 : sucess
@return - 负数 : failed
i2c_master_send()
发送一个i2c消息(内核源码/include/linux/i2c.h)
static inline int i2c_master_send(const struct i2c_client *client,const char *buf, int count){return i2c_transfer_buffer_flags(client, (char *)buf, count, 0);};
i2c_master_recv()
接收一个i2c消息(内核源码/include/linux/i2c.h)
static inline int i2c_master_recv(const struct i2c_client *client,char *buf, int count){return i2c_transfer_buffer_flags(client, buf, count, I2C_M_RD);};
i2c_transfer_buffer_flags()
发送一个i2c消息(内核源码/drivers/i2c/i2c-core-base.c)
int i2c_transfer_buffer_flags(const struct i2c_client *client, char *buf,int count, u16 flags){int ret;struct i2c_msg msg = {.addr = client->addr,.flags = flags | (client->flags & I2C_M_TEN),.len = count,.buf = buf,};ret = i2c_transfer(client->adapter, &msg, 1);/** If everything went ok (i.e. 1 msg transferred), return #bytes* transferred, else error code.*/return (ret == 1) ? count : ret;}
spi总线操作
左边的总线驱动厂商已经写好了,我们只要写右边的设备驱动
spi_驱动里有关的结构体
struct spi_driver spi设备驱动结构体
struct spi_driver (内核源码/include/linux/spi/spi.h)
struct spi_driver {const struct spi_device_id *id_table; //用来和spi进行配对int (*probe)(struct spi_device *spi); //spi设备和spi驱动匹配成功后,回调该函数指针int (*remove)(struct spi_device *spi);void (*shutdown)(struct spi_device *spi);struct device_driver driver;
};
struct spi_device spi设备结构体
struct spi_device (内核源码/include/linux/spi/spi.h)
struct spi_device {struct device dev; //device类型结构体,spi设备结构体、i2c设备结构体、平台设备结构体都是“继承”自设备结构体struct spi_controller *controller;//当前spi设备挂载在那个spi控制器struct spi_controller *master;//在总线驱动中,一个spi_master代表了一个spi总线,用于指定spi设备挂载到那个spi总线上u32 max_speed_hz;//SPI通信的最大频率u8 chip_select;//spi总选用于区分不同SPI设备的一个标号u8 bits_per_word;/SPI通信时一个字节多少位,也就是传输单位u16 mode;//SPI工作模式,工作模式如以上代码中的宏定义
#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;void *controller_data;char modalias[SPI_NAME_SIZE];int cs_gpio;//片选引脚,驱动和设别树节点匹配成功后自动获取片选引脚,也可以自定义片选引脚struct spi_statistics statistics;//记录spi名字,用来和spi_driver进行配对};
struct spi_transfer spi传输结构体
在spi设备驱动程序中,spi_transfer结构体用于指定要发送的数据
struct spi_transfer {/* it's ok if tx_buf == rx_buf (right?)* for MicroWire, one buffer must be null* buffers must work with dma_*map_single() calls, unless* spi_message.is_dma_mapped reports a pre-existing mapping*/const void *tx_buf;//发送缓冲区void *rx_buf;//接受缓冲区unsigned len;//接收或发送的长度,由于spi特性,发送和接收长度相等dma_addr_t tx_dma;//如果用了DMA,指定tx DMA地址dma_addr_t rx_dma;//如果用了DMA,指定rx DMA地址struct sg_table tx_sg;//struct sg_table rx_sg;unsigned cs_change:1;unsigned tx_nbits:3;unsigned rx_nbits:3;
#define SPI_NBITS_SINGLE 0x01 /* 1bit transfer */
#define SPI_NBITS_DUAL 0x02 /* 2bits transfer */
#define SPI_NBITS_QUAD 0x04 /* 4bits transfer */u8 bits_per_word;//指定字节多少位,频率,带有默认值u16 delay_usecs;u32 speed_hz;struct list_head transfer_list;
};
struct spi_message spi消息结构体
spi_transfer结构体保存了要发送(或接收)的数据,而在SPI设备驱动中数据是以“消息”的形式发送。 spi_message是消息结构体,我们把它称为消息结构体,发送一个消息分四步, 依次为定义消息结构体、初始化消息结构体、“绑定”要发送的数据(也就是初始化好的spi_transfer结构)、执行发送。
struct list_head transfers;struct spi_device *spi;unsigned is_dma_mapped:1;/* REVISIT: we might want a flag affecting the behavior of the* last transfer ... allowing things like "read 16 bit length L"* immediately followed by "read L bytes". Basically imposing* a specific message scheduling algorithm.** Some controller drivers (message-at-a-time queue processing)* could provide that as their default scheduling algorithm. But* others (with multi-message pipelines) could need a flag to* tell them about such special cases.*//* completion is reported through a callback */void (*complete)(void *context);void *context;unsigned frame_length;unsigned actual_length;int status;/* for optional use by whatever driver currently owns the* spi_message ... between calls to spi_async and then later* complete(), that's the spi_master controller driver.*/struct list_head queue;void *state;
};
spi_message结构体成员我们比较陌生,如果我们不考虑具体的发送细节我们可以不用了解这些成员的含义,因为spi_message的初始化以及“绑定”spi_transfer传输结构体都是由内核函数实现。 唯一要说明的是第二个成员“spi”,它是一个spi_device类型的指针,我们讲解spi_device结构体时说过,一个spi设备对应一个spi_device结构体,这个成员就是用于指定消息来自哪个设备。
各种spi的总线函数(了解,我们自己不怎么用)
spi_init(void) spi总线注册函数
linux系统在开机的时候就会执行,自动进行spi总线注册。
spi总线注册 (内核源码/drivers/spi/spi.c)
static int __init spi_init(void)
{int status;...status = bus_register(&spi_bus_type);...status = class_register(&spi_master_class);...
}
spi_match_device() spi设备匹配函数
函数提供了四种匹配方式,设备树匹配方式和acpi匹配方式以及id_table匹配方式,如果前面三种都没有匹配成功,则通过设备名进行配对。
spi总线注册 (内核源码/drivers/spi/spi.c)
static int spi_match_device(struct device *dev, struct device_driver *drv)
{const struct spi_device *spi = to_spi_device(dev);const struct spi_driver *sdrv = to_spi_driver(drv);/* Attempt an OF style match */if (of_driver_match_device(dev, drv))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;
}
spi_imx_probe() 强大的初数化函数
spi_imx_probe()函数主要有如下功能: 获取设备树节点信息,初始化spi时钟、dma、中断等, 保存spi寄存器起始地址,填充spi控制器回调函数。
spi_imx_probe函数 (内核源码/drivers/spi/spi-imx.c)
static int spi_imx_probe(struct platform_device *pdev)
{struct device_node *np = pdev->dev.of_node;const struct of_device_id *of_id =of_match_device(spi_imx_dt_ids, &pdev->dev);struct spi_imx_master *mxc_platform_info =dev_get_platdata(&pdev->dev);struct spi_master *master;struct spi_imx_data *spi_imx;struct resource *res;const struct spi_imx_devtype_data *devtype_data = of_id ? of_id->data :(struct spi_imx_devtype_data *)pdev->id_entry->driver_data;bool slave_mode;...slave_mode = devtype_data->has_slavemode &&of_property_read_bool(np, "spi-slave");if (slave_mode)master = spi_alloc_slave(&pdev->dev,sizeof(struct spi_imx_data));elsemaster = spi_alloc_master(&pdev->dev,sizeof(struct spi_imx_data));if (!master)return -ENOMEM;...ret = of_property_read_u32(np, "fsl,spi-num-chipselects", &num_cs);if (ret < 0) {if (mxc_platform_info) {num_cs = mxc_platform_info->num_chipselect;master->num_chipselect = num_cs;}} else {master->num_chipselect = num_cs;}spi_imx = spi_master_get_devdata(master);spi_imx->bitbang.master = master;spi_imx->dev = &pdev->dev;spi_imx->slave_mode = slave_mode;spi_imx->devtype_data = devtype_data;master->cs_gpios = devm_kzalloc(&master->dev,sizeof(int) * master->num_chipselect, GFP_KERNEL);spi_imx->bitbang.chipselect = spi_imx_chipselect;spi_imx->bitbang.setup_transfer = spi_imx_setupxfer;spi_imx->bitbang.txrx_bufs = spi_imx_transfer;spi_imx->bitbang.master->setup = spi_imx_setup;spi_imx->bitbang.master->cleanup = spi_imx_cleanup;spi_imx->bitbang.master->prepare_message = spi_imx_prepare_message;spi_imx->bitbang.master->unprepare_message = spi_imx_unprepare_message;spi_imx->bitbang.master->slave_abort = spi_imx_slave_abort;spi_imx->bitbang.master->mode_bits = SPI_CPOL | SPI_CPHA | SPI_CS_HIGH \...init_completion(&spi_imx->xfer_done);res = platform_get_resource(pdev, IORESOURCE_MEM, 0);spi_imx->base = devm_ioremap_resource(&pdev->dev, res);...ret = spi_bitbang_start(&spi_imx->bitbang);...
}
第2-13行: 声明一些必要的变量,这些结构体的关系如上所示。
第16行: of_property_read_bool()去设备树节点读取指定的值保存在slave_mode中,spi-slave的值指定spi是工作在主模式还是从模式下面。
第17-24行: 根据slave_mode的值为master分配内存。
第26-34行:读取spi片选数量,保存在num_cs中,然后将值赋给master。使用时,就可以通过spi_controller获取片选信号。
第37行:将spi_bitbang结构体指向spi控制器,spi_bitbang的作用就是让我们使用普通GPIO来模拟spi时序。由于我们不使用普通GPIO模拟spi,这部分就不深入讲解
第38-41行: 对spi_imx其他成员进行初始化。
第43行: 对master中的cs_gpios动态申请内存。用来存放spi控制器所使用的引脚。
第46-54行: 为spi_bitbang设置了一系列的回调函数,这里重要的有spi_imx_setup、spi_imx_cleanup、spi_imx_prepare_message、spi_imx_unprepare_message,后面会一一介绍。
第58、59行: 获取设备树spi控制器的基地址,并进行虚拟地址映射,存放现在spi_imx结构体变量中的base成员中。
第61行: 接下来调用spi_bitbang_start函数传入bitbang指针
spi设备驱动(在驱动里使用)
spi_register_driver() spi设备注册函数
相同的还有注销函数
int spi_register_driver(struct spi_driver *sdrv)
static inline void spi_unregister_driver(struct spi_driver *sdrv)
@param - struct spi_driver *sdrv : spi类型的结构体
@return - 0 :sucess
@return - 其他值 : failed
spi_setup() spi驱动设置函数
函数设置spi设备的片选信号、传输单位、最大传输速率等,函数中调用spi控制器的成员controller->setup(), 也就是spi_imx->bitbang.master->setup(),在函数spi_imx_probe()中我们将spi_imx_setup赋予该结构体。
spi_setup函数(内核源码/drivers/spi/spi.c)
int spi_setup(struct spi_device *spi)
@param - struct spi_device *spi : spi结构体
@return - 0 :sucess
@return - 其他值 : failed
spi_message_init() 消息队列初始化函数
spi_message_init函数(内核源码/include/linux/spi/spi.h)
static inline void spi_message_init(struct spi_message *m)
{memset(m, 0, sizeof *m);spi_message_init_no_memset(m);
}@param - m : spi_message 结构体指针,spi_message结构体定义和介绍可在前面关键数据结构中找到。
@return : NULL
spi_message_add_tail() 添加消息队列函数
spi_message_init函数(内核源码/include/linux/spi/spi.h)
这个函数很简单就是将将spi_transfer结构体添加到spi_message队列的末尾。
static inline void spi_message_add_tail(struct spi_transfer *t, struct spi_message *m)
{list_add_tail(&t->transfer_list, &m->transfers);
}
中断系统
request_irq() 中断注册函数
static inline int __must_check request_irq(unsigned int irq, irq_handler_t handler,unsigned long flags, const char *name, void *dev)@param - unsigned int irq : 指定“内核中断号”,这个参数我们会从设备树中获取或转换得到。在内核空间中它代表一个唯一的中断编号
@param - irq_handler_t handler : 用于指定中断处理函数,中断发生后跳转到该函数去执行
@param - unsigned long flags : 中断触发条件,写在下面
@param - const char *name : 中断的名字,中断申请成功后会在“/proc/interrupts”目录下看到对应的文件
@param - void *dev : 如果使用了**IRQF_SHARED** 宏,则开启了共享中断
@return - 0 :sucess
@return - 负数 : failed
#define IRQF_TRIGGER_NONE 0x00000000
#define IRQF_TRIGGER_RISING 0x00000001
#define IRQF_TRIGGER_FALLING 0x00000002
#define IRQF_TRIGGER_HIGH 0x00000004
#define IRQF_TRIGGER_LOW 0x00000008
#define IRQF_TRIGGER_MASK (IRQF_TRIGGER_HIGH | IRQF_TRIGGER_LOW | \IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING)
#define IRQF_TRIGGER_PROBE 0x00000010#define IRQF_SHARED 0x00000080 ---------①
free_irq()中断注销函数
void free_irq(unsigned int irq, void *dev);
@param - unsigned int irq : 从设备树中得到或者转换得到的中断编号
@param - void *dev : 与request_irq函数中dev传入的参数一致
@return - NULL
irqreturn_t() 中断处理函数
在中断申请时需要指定一个中断处理函数
@param - unsigned int irq : 用于指定"内核中断号"
@param - void * dev : 在共享中断中,用来判断中断产生的驱动是哪个
@return - irqreturn_t : enum irqreturn {IRQ_NONE = (0 << 0),IRQ_HANDLED = (1 << 0),IRQ_WAKE_THREAD = (1 << 1),
};typedef enum irqreturn irqreturn_t;
设备树常用奇奇怪怪速查相关推荐
- pandas常用函数说明及速查表
pandas常用函数说明及速查表 如果你用python做开发,那么几乎肯定会使用pandas库. Pandas 是 Python 语言的一个扩展程序库,用于数据分析. Pandas 是一个开放源码.B ...
- 《zw版·Halcon-delphi系列原创教程》 zw版-Halcon常用函数Top100中文速查手册
<zw版·Halcon-delphi系列原创教程> zw版-Halcon常用函数Top100中文速查手册 Halcon函数库非常庞大,v11版有1900多个算子(函数). 这个Top版,对 ...
- C语言奇奇怪怪表达式‘abcd‘,及操作符详解
前言 回顾操作符和一些表达式方面的知识. 表达式及操作符 前言 算术操作符 : + - * / 位操作符 >>.<< >> 算数右移 逻辑右移 << 小 ...
- Hadoop常用的操作指令速查和讲解
一.常用操作指令速查 假设Hadoop的安装目录HADOOP_HOME为/home/admin/hadoop.0.启动与关闭 启动Hadoop 进入HADOOP_HOME目录. 执行sh bin/st ...
- 古有陈天华万字血书抗沙俄,今有本剧蒻万字背包虐dp(01,完全,多重,分组,混合等各种背包详解 + 板子题+ 奇奇怪怪滴变式题)
前言: 本文介绍了01背包.完全背包.多重背包.混合背包.分组背包等背包,并对其进行透彻的剖析,并附上了板子题,供您白嫖,以及一些奇葩变式,颇有意思,供你琢磨玩弄.此外绝大部分题都有二维数组和滚动数组 ...
- 【奇奇怪怪小知识】系统盘(C盘)持续发出“救救我”信号(C盘仅剩不足10G,爆红), 操作过后直接腾出近20G
一.系统磁盘状况 新买的电脑不到两年,平常安装都放去其他磁盘.结果今天一看,我去?C盘变红彤彤的? C盘仅剩不到10G,爆红!c盘似乎在跟我说,救救我!救救我! 无奈之下,我开始与之拉扯,看看都有啥? ...
- 奇奇怪怪技术贴之软件账户注销
窃以为好的软件在注销账户上也要做到完善.近日对几个常用软件的账户注销功能做了测试. 1.百度 登录账号即可注销,5分好评. 流程:登录手机百度App--我的--设置--账号管理--账号急救--账号注销 ...
- [算法][算法复杂度]常用算法复杂度速查表
复杂度通常会使用大 -O记号来表示,比如快速排序的平均时间复杂度是 O(nlog(n)).虽然我们应该做「理解派」,但是即使每个算法/数据结构都理解了,不时仍有可能忘记具体某个算法/数据结构的复杂度( ...
- 常用游戏类型简写速查
ACT......(ACTION GAME )动作游戏 STG......(SHOTING GAME )射击游戏 RPG......(ROLE PLAYING GAME )角色扮演游戏 A.RPG.. ...
最新文章
- C/C++中constkeyword
- 如何实现java虚拟机的优化_Java虚拟机JVM优化实战的过程全记录
- android:persistent (非系统app失效)
- linux加密框架 crypto 算法管理 - 动态和静态算法管理
- 下半年登场!小米MIX 4概念图曝光:有望首发屏下摄像头
- 如何经由PHP获得MySQL procedure结果
- mtk android 编译环境,MTK android 快速编译方法.docx
- 手把手教你接入支付宝支付
- matlab发送mavlink消息
- 【k8s】Unable to restart cluster, will reset it: apiserver healthz异常
- 小熊派移植华为 LiteOS-M(基于MDK)
- Qt中使用QAxObject的dynamicCall和querySubObject函数操作SolidWorks的方法
- 计算机语言热门有,百度传课发布2月热门榜单,计算机和语言类大热
- 使用深度学习的图像分割(综述)
- paddle 学习总结与使用指南 笔记(一)
- 利用InstallAnywhere制作JAVA可执行程序安装文件
- SendMessage参数大全
- VSCode不能跳转到定义的解决方法
- JDBC连接ORACLE的三种URL格式
- 常见的数据库错误的解决方法!必看!(含安装插件常见错误)