Spi Subsystem

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,因为它们都是在上升沿采样数据,不用去在乎时钟的初始电平是什么,只要在上升沿采集数据就行。 SPI driver spi_controller struct spi_controller { struct device dev; struct list_head list; s16 bus_num; u16 num_chipselect; u16 dma_alignment; u32 mode_bits; u32 buswidth_override_bits; u32 bits_per_word_mask; u32 min_speed_hz; u32 max_speed_hz; u16 flags; bool devm_allocated; union { bool slave; bool target; }; size_t (*max_transfer_size)(struct spi_device *spi); size_t (*max_message_size)(struct spi_device *spi); struct mutex io_mutex; struct mutex add_lock; spinlock_t bus_lock_spinlock; struct mutex bus_lock_mutex; bool bus_lock_flag; int (*setup)(struct spi_device *spi); int (*set_cs_timing)(struct spi_device *spi); int (*transfer)(struct spi_device *spi, struct spi_message *mesg); void (*cleanup)(struct spi_device *spi); bool (*can_dma)(struct spi_controller *ctlr, struct spi_device *spi, struct spi_transfer *xfer); struct device *dma_map_dev; struct device *cur_rx_dma_dev; struct device *cur_tx_dma_dev; bool queued; struct kthread_worker *kworker; struct kthread_work pump_messages; spinlock_t queue_lock; struct list_head queue; struct spi_message *cur_msg; struct completion cur_msg_completion; bool cur_msg_incomplete; bool cur_msg_need_completion; bool busy; bool running; bool rt; bool auto_runtime_pm; bool cur_msg_mapped; bool fallback; bool last_cs_mode_high; s8 last_cs[SPI_CS_CNT_MAX]; u32 last_cs_index_mask : SPI_CS_CNT_MAX; struct completion xfer_completion; size_t max_dma_len; int (*optimize_message)(struct spi_message *msg); int (*unoptimize_message)(struct spi_message *msg); int (*prepare_transfer_hardware)(struct spi_controller *ctlr); int (*transfer_one_message)(struct spi_controller *ctlr, struct spi_message *mesg); int (*unprepare_transfer_hardware)(struct spi_controller *ctlr); int (*prepare_message)(struct spi_controller *ctlr, struct spi_message *message); int (*unprepare_message)(struct spi_controller *ctlr, struct spi_message *message); union { int (*slave_abort)(struct spi_controller *ctlr); int (*target_abort)(struct spi_controller *ctlr); }; void (*set_cs)(struct spi_device *spi, bool enable); int (*transfer_one)(struct spi_controller *ctlr, struct spi_device *spi, struct spi_transfer *transfer); void (*handle_err)(struct spi_controller *ctlr, struct spi_message *message); const struct spi_controller_mem_ops *mem_ops; const struct spi_controller_mem_caps *mem_caps; struct gpio_desc **cs_gpiods; bool use_gpio_descriptors; s8 unused_native_cs; s8 max_native_cs; struct spi_statistics __percpu *pcpu_statistics; struct dma_chan *dma_tx; struct dma_chan *dma_rx; void *dummy_rx; void *dummy_tx; int (*fw_translate_cs)(struct spi_controller *ctlr, unsigned cs); bool ptp_sts_supported; unsigned long irq_flags; bool queue_empty; bool must_async; bool defer_optimize_message; }; list: spi controller global list。...

2023-05-22 · 6 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....

2023-05-15 · 5 min

GPIO Subsystem

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

Consumer APIs: bool pinctrl_gpio_can_use_line(unsigned gpio); int pinctrl_gpio_request(unsigned gpio); void pinctrl_gpio_free(unsigned gpio); int pinctrl_gpio_direction_input(unsigned gpio); int pinctrl_gpio_direction_output(unsigned gpio); int pinctrl_gpio_set_config(unsigned gpio, unsigned long config); pinctrl_gpio_can_use_line: 如果 dts 没有定义 gpio-ranges-group-names, 将 gpio index 映射到 pin index, 总是返回 true。\ 统一驱动设备模型会处理 pin control: platform_driver_register() driver_register(); bus_add_driver(); driver_attach(); __driver_attach(); device_driver_attach(drv, dev); driver_probe_device(drv, dev); really_probe(dev, drv); really_probe(struct device *dev, struct device_driver *drv) pinctrl_bind_pins(dev); devm_pinctrl_get(dev); pinctrl_lookup_state(dev->pins->p, PINCTRL_STATE_DEFAULT); pinctrl_lookup_state(dev->pins->p, PINCTRL_STATE_INIT); pinctrl_select_state(dev->pins->p, dev->pins->default_state); Provider Debug files /sys/kernel/debug/pinctrl Reference https://docs.kernel.org/driver-api/pin-control.html

2023-05-10 · 1 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。...

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。...

2023-03-21 · 4 min