Spi Subsystem

1. SPI协议介绍 CPOL:表示SPICLK的初始电平,0为低电平,1为高电平 CPHA:表示相位,即第一个还是第二个时钟沿采样数据,0为第一个时钟沿,1为第二个时钟沿 CPOL CPHA 模式 含义 0 0 0 SPICLK初始电平为低电平,在第一个时钟沿采样数据 0 1 1 SPICLK初始电平为低电平,在第二个时钟沿采样数据 1 0 2 SPICLK初始电平为高电平,在第一个时钟沿采样数据 1 1 3 SPICLK初始电平为高电平,在第二个时钟沿采样数据 我们常用的是模式0和模式3,因为它们都是在上升沿采样数据,不用去在乎时钟的初始电平是什么,只要在上升沿采集数据就行。 2. SPI driver 2.2 spi_controller include/linux/spi.h 2.3 spi_device include/linux/spi.h 2.4 spi_transfer、spi_message 在SPI子系统中,用spi_transfer结构体描述一个传输,用spi_message管理整个传输。可以构造多个spi_transfer结构体,把它们放入一个spi_message里面。 SPI传输时,发出N个字节,就可以同时得到N个字节。 即使只想读N个字节,也必须发出N个字节:可以发出0xff 即使只想发出N个字节,也会读到N个字节:可以忽略读到的数据。 3. SPI 设备树处理 SPI Master 必须的属性如下: address-cells:这个SPI Master下的SPI设备,需要多少个cell来表述它的片选引脚 size-cells:必须设置为0 compatible:根据它找到SPI Master驱动 可选的属性如下: cs-gpios:SPI Master可以使用多个GPIO当做片选,可以在这个属性列出那些GPIO num-cs:片选引脚总数 SPI Device 必须的属性如下: compatible:根据它找到SPI Device驱动 reg:用来表示它使用哪个片选引脚 spi-max-frequency:必选,该SPI设备支持的最大SPI时钟 可选的属性如下: spi-cpol:这是一个空属性(没有值),表示CPOL为1,即平时SPI时钟为低电平 spi-cpha:这是一个空属性(没有值),表示CPHA为1),即在时钟的第2个边沿采样数据 spi-cs-high:这是一个空属性(没有值),表示片选引脚高电平有效 spi-3wire:这是一个空属性(没有值),表示使用SPI 三线模式 spi-lsb-first:这是一个空属性(没有值),表示使用SPI传输数据时先传输最低位(LSB) spi-tx-bus-width:表示有几条MOSI引脚;没有这个属性时默认只有1条MOSI引脚 spi-rx-bus-width:表示有几条MISO引脚;没有这个属性时默认只有1条MISO引脚 spi-rx-delay-us:单位是毫秒,表示每次读传输后要延时多久 spi-tx-delay-us:单位是毫秒,表示每次写传输后要延时多久 示例:...

2023-05-22 · 2 min

Linux Driver Common Essentials

NOT indexed include <asm/xxx.h> 先找arch/xxx/include/xxx.h,没有的话就找/include/asm-generic/xxx.h 从dts中获取regs地址并映射到virtual address: linux5.10: void __iomem *devm_platform_get_and_ioremap_resource(struct platform_device *pdev, unsigned int index, struct resource **res) linux5.4: void __iomem *devm_platform_ioremap_resource(struct platform_device *pdev, unsigned int index) 相当于platform_get_resource + devm_request_mem_region + devm_ioremap linux链表相关操作: #define list_entry(ptr, type, member) \ /// list_entry作用和container_of相同 container_of(ptr, type, member) container_of(ptr, type, member) // ptr:表示结构体中member的地址h(已知的) // type:表示结构体类型 struct xxx // member:表示结构体中的成员 yyy // 返回结构体的首地址 /** * list_for_each_entry - iterate over list of given type * @pos: the type * to use as a loop cursor....

2023-05-19 · 1 min

Uart Subsystem

Reference https://www.kernel.org/doc/Documentation/serial/driver Introduction 波特率115200,bps每秒传输的bit数。 每一位1/115200秒,传输1byte需要10位(start, data, stop),那么每秒能传11520byte。 115200,8n1。8:data,n:校验位不用,1:停止位。 TTY体系中设备节点的差别 不关心终端是真实的还是虚拟的,都可以通过/dev/tty找到当前终端。 /dev/console 内核的打印信息可以通过cmdline来选择打印到哪个设备。 console=ttyS0 console=tty console=ttyS0时,/dev/console就是ttyS0 console=ttyN时,/dev/console就是/dev/ttyN console=tty时,/dev/console就是前台程序的虚拟终端 console=tty0时,/dev/console就是前台程序的虚拟终端 console有多个取值时,使用最后一个取值来判断。 /dev/tty 和/dev/tty0区别 /dev/tty表示当前进程的控制终端,也就是当前进程与用户交互的终端。 /dev/tty0则是当前所使用虚拟终端的一个别名 Linux串口应用编程 https://digilander.libero.it/robang/rubrica/serial.htm struct termios options; open("/dev/ttyS1", O_RDWR | O_NOCTTY | O_NDELAY)// O_NOCTTY: 不用作控制终端 O_NDELAY: 使I/O变成非阻塞模式 fcntl(fd, F_SETFL, 0): //读数据时,没有数据则阻塞等待 fcntl(fd, F_SETFL, FNDELAY): //读数据时不等待,没有数据就返回0 /* c_cflag: Control Options */ options.c_cflag |= (CLOCAL | CREAD); // 必须打开 Enable the receiver and set local mode options.c_cflag &= ~CSIZE; /* Mask the character size bits */ options....

2023-05-15 · 3 min

GPIO Subsystem

https://zhuanlan.zhihu.com/p/41942876 push pull推挽输出 推挽输出的最大特点是可以真正能真正的输出高电平和低电平,在两种电平下都具有驱动能力。 open drain开漏输出 open source开集输出 这两种输出的原理和特性基本是类似的,区别在于一个是使用MOS管,其中的"漏"指的就是MOS管的漏极;另一个使用三极管,其中的"集"指的就是MOS三极管的集电极。这两者其实都是和推挽输出相对应的输出模式,由于使用MOS管的情况较多,很多时候就用"开漏输出"这个词代替了开漏输出和开集输出。 开漏、开集输出最主要的特性就是高电平没有驱动能力,需要借助外部上拉电阻才能真正输出高电平。 Kernel doc: General Purpose Input/Output (GPIO) GPIO Driver Interface Controller Drivers: gpio_chip struct gpio_chip 抽象gpio controller。 gpiochip_add_data() or devm_gpiochip_add_data()接口用来注册gpio controller。gpiochip_remove()释放gpio controller。 gpiochip_is_request()在gpio controller driver中用于检测某个gpio是否被其他chip占用,没占用返回NULL,占用返回request时传入的string。 GPIO electrical configuration gpio_chip的.set_config回调用于设置: Debouncing Single-ended modes (open drain/open source) Pull up and pull down resistor enablement 这些属性可以在dts中指定,include/dt-bindings/gpio/gpio.h GPIO_PUSH_PULL GPIO_LINE_OPEN_SOURCE GPIO_OPEN_DRAIN … 可以设置为gpiochip_generic_config()会调用到pinctrl_gpio_set_config()->ops->pin_config_set GPIO drivers providing IRQs gpiod_to_irq(); // 传入gpio_desc,返回gpio的irq number(软件映射的,不是irq hw id) gpio_chip_hwgpio(); gc->to_irq(); rts_gpio_to_irq(); irq_linear_revmap(); Cascaded GPIO irqchips CHAINED CASCADED GPIO IRQCHIPS:挺多soc上是这种做法,打开CONFIG_GPIOLIB_IRQCHIP设置girq->parent_handler。gpio controller注册过程中通过irq_set_chained_handler设置中断处理函数,因此在中断处理函数中需要chained_irq_enter,chained_irq_exit。相当于级联中断处理器的做法。 static irqreturn_t foo_gpio_irq(int irq, void *data) /// 中断处理函数 chained_irq_enter(....

2023-05-12 · 2 min

Pinctrl Subsystem

/sys/kernel/debug/pinctrl 其他驱动调用pinctrl子系统: #include <linux/pinctrl/consumer.h> static struct pinctrl *xxx_pinctrl; struct pinctrl_state *default_state = NULL; // dts pinctrl-0 = <&state1> pinctrl-1 = <&state2> 1. /* 获取pin control state holder 的句柄 */ pinctrl = devm_pinctrl_get(dev); 2. /* 得到名字为state1和state2对应的pin state */ **struct** pinctrl_state * turnon_tes = pinctrl_lookup_state(pinctrl, "state1"); **struct** pinctrl_state * turnoff_tes = pinctrl_lookup_state(pinctrl, "state2"); 3. pinctrl_select_state(pinctrl, turnon_tes)。 devm_pinctrl_get(struct device *dev) //返回一个pinctrl句柄 pinctrl_get(struct device *dev) find_pinctrl(struct device *dev) // 查看是否device core已经创建了该pinctrl句柄 create_pinctrl(struct device *dev, struct pinctrl_dev *pctldev) pinctrl_dt_to_map(struct pinctrl *p, struct pinctrl_dev *pctldev) //从设备树中获取信息保存到pinctrl_map结构体中 for (state = 0; ; state++) { propname = kasprintf(GFP_KERNEL, "pinctrl-%d", state); //查找pinctrl-0,1,2属性 //size保存了pinctrl-0中phandle的个数,比如有的节点pinctrl-0 = <&x1, &x2> prop = of_find_property(np, propname, &size); list = prop->value; //list保存了phandle列表 //保存pinctrl-names index为state的name,比如pinctrl-names = "default" ret = of_property_read_string_index(np, "pinctrl-names", state, &statename); if (ret < 0) // 如果没有定义pinctrl-names属性,那么我们将pinctrl-0 pinctrl-1 pinctrl-2……中的那个ID取出来作为state name statename = prop->name + strlen("pinctrl-"); for (config = 0; config < size; config++) { phandle = be32_to_cpup(list++); np_config = of_find_node_by_phandle(phandle); // 找到pinctrl-x=<&x1, &x2>中的config节点 dt_to_map_one_config(p, pctldev, statename, np_config); np_pctldev = of_node_get(np_config); np_pctldev = of_get_next_parent(np_pctldev); // 找到pinctrl controller节点 pctldev = get_pinctrl_dev_from_of_node(np_pctldev); //找到pinctrl_dev //调用底层的callback函数处理pin configuration node。 ops->dt_node_to_map(pctldev, np_config, &map, &num_maps); ....

2023-05-10 · 2 min

Time Subsystem

CONFIG_GENERIC_CLOCKEVENTS:新的时间子系统 以下选项三选一: CONFIG_HZ_PERIODIC:无论何时,都启用用周期性的tick,即便是在系统idle的时候。 CONFIG_NO_HZ_IDLE:在系统idle的时候,停掉周期性tick。会同时enable NO_HZ_COMMON。 CONFIG_NO_HZ_FULL:即便在非idle的状态下,也就是说cpu上还运行在task,也可能会停掉tick。会同时enable NO_HZ_COMMON。 CONFIG_HIGH_RES_TIMERS:高精度timer。 如果配置了高精度timer,或者配置了NO_HZ_COMMON的选项,那么一定需要配置CONFIG_TICK_ONESHOT,表示系统支持支持one-shot类型的tick device。 sysfs接口 cd /sys/bus/clocksource/devices/clocksource0 cat current_clocksource: 查看当前的clocksource cat available_clocksource: 查看可用的clocksource echo xxx > current_clocksource: 设置clocksource cd /sys/bus/clockevents/devices/clockevent0 cat current_device: 查看当前的clockevent Clocksource Clockevent

2023-05-08 · 1 min

Interrupt Subsystem

IRQ domain http://www.wowotech.net/linux_kenrel/irq-domain.html 1. 向系统注册irq domain interrupt controller初始化的过程中,注册irq domain irq_domain_add_linear(struct device_node *of_node, unsigned int size, const struct irq_domain_ops *ops, void *host_data) 2. 为irq domain创建映射 在各个硬件外设的驱动初始化过程中,创建HW interrupt ID和IRQ number的映射关系。 方法1:irq_create_mapping(struct irq_domain *host, irq_hw_number_t hwirq); 比如drivers/clocksource/timer-riscv.c中irq_create_mapping(domain, RV_IRQ_TIMER);直接将hw id(RV_IRQ_TIMER)传入, 创建hw id和irq number的映射。 irq_create_mapping(domain, hwirq); irq_create_mapping_affinity(); irq_domain_alloc_descs(); // 创建hw id和irq number的映射 irq_domain_associate(); domain->ops->map(); //调用到interrupt controller的map函数 方法2:irq_of_parse_and_map. 需要在设备树中指定hw id。 比如drivers/irqchip/irq-realtek-plic.c中irq_of_parse_and_map irq_of_parse_and_map(struct device_node *dev, int index); of_irq_parse_one(dev, index, &oirq); // 解析设备树 irq_create_of_mapping(&oirq); irq_create_fwspec_mapping(); irq_create_mapping(domain, hwirq); // 最终还是调用到irq_create_mapping 方法3:外设driver中直接platform_get_irq...

2023-05-08 · 3 min

I2C Subsystem

用户层测试指令 # 检测当前系统有几组i2c总线 i2cdetect -l # 查看i2c-0接口上的设备 i2cdetect -y -a 0 # Force scanning of non-regular addresses i2cdetect -y -r 0 # 读取指定设备的全部寄存器的值 i2cdump -f -y 0 0x68 # 读取指定i2c设备的某个寄存器的值,如下读取i2c-0地址为0x68器件中的0x01寄存器 i2cget -f -y 0 0x68 0x01 # 写入指定i2c设备的某个寄存器的值,如下写入i2c-0地址为0x68器件中的0x01寄存器值为0x6f i2cset -f -y 0 0x68 0x01 0x6f # 写入i2c-0地址为0x50的eeprom,从偏移为0x64地址读8个byte。 i2ctransfer -f -y 0 w1@0x50 0x64 r8 I2C 基础知识 写操作 主芯片要发出一个start信号 然后发出一个设备地址(用来确定是往哪一个芯片写数据),方向(读/写,0表示写,1表示读) 从设备回应(用来确定这个设备是否存在),然后就可以传输数据 主设备发送一个字节数据给从设备,并等待回应 每传输一字节数据,接收方要有一个回应信号(确定数据是否接受完成),然后再传输下一个数据。 数据发送完之后,主芯片就会发送一个停止信号。 下图:白色背景表示"主→从",灰色背景表示"从→主" 读操作 I2C信号 I2C协议中数据传输的单位是字节,也就是8位。但是要用到9个时钟:前面8个时钟用来传输8数据,第9个时钟用来传输回应信号。传输时,先传输最高位(MSB)。 开始信号(S):SCL为高电平时,SDA山高电平向低电平跳变,开始传送数据。 结束信号(P):SCL为高电平时,SDA由低电平向高电平跳变,结束传送数据。 响应信号(ACK):接收器在接收到8位数据后,在第9个时钟周期,拉低SDA SDA上传输的数据必须在SCL为高电平期间保持稳定,SDA上的数据只能在SCL为低电平期间变化 I2C协议信号如下:...

2023-04-13 · 4 min

PWM Subsystem

PWM 子系统 PWM原理 利用微处理器的数字输出来对模拟电路进行控制的一种非常有效的技术,其本质是一种对模拟信号电平进行数字编码的方法。在嵌入式设备中,PWM多用于控制马达、LED、振动器等模拟器件。 脉冲周期(T),单位是时间,ns, us ,ms。 脉冲频率(f),单位是赫兹(Hz),与脉冲周期成倒数关系,f=1/T。 脉冲宽度(W),简称“脉宽”,是脉冲高电平持续的时间。单位是时间,ns, us, ms。 占空比(D),脉宽除以脉冲周期的值。 W = ton T = ton + toff = 1/f D = ton / (ton+ toff) = ton / T PWM consumer /* include/linux/pwm.h */ int pwm_config(struct pwm_device *pwm, int duty_ns, int period_ns); int pwm_enable(struct pwm_device *pwm); void pwm_disable(struct pwm_device *pwm); int pwm_apply_state(struct pwm_device *pwm, const struct pwm_state *state); pwm_config,用于控制PWM输出信号的频率和占空比,其中频率是以周期(period_ns)的形式配置的,占空比是以有效时间(duty_ns)的形式配置的。 pwm_enable/pwm_disable,用于控制PWM信号输出与否。 pwm_apply_state需要定义一个pwm_state,可以一下子修改period/duty_cycle/polarity/enabled。 上面的API都以struct pwm_device类型的指针为操作句柄,该指针抽象了一个PWM设备,那么怎么获得PWM句柄呢?使用如下的API: /* include/linux/pwm.h */ struct pwm_device *pwm_get(struct device *dev, const char *con_id); struct pwm_device *of_pwm_get(struct device_node *np, const char *con_id); void pwm_put(struct pwm_device *pwm); struct pwm_device *devm_pwm_get(struct device *dev, const char *con_id); struct pwm_device *devm_of_pwm_get(struct device *dev, struct device_node *np, const char *con_id); void devm_pwm_put(struct device *dev, struct pwm_device *pwm); pwm_get/devm_pwm_get,从指定设备(dev)的DTS节点中,获得对应的PWM句柄。可以通过con_id指定一个名称,或者会获取和该设备绑定的第一个PWM句柄。设备的DTS文件需要用这样的格式指定所使用的PWM device:...

2023-03-21 · 3 min