
文章目录1.协议1.1 i2ctools2.模型2.1 I2C设备添加用户空间注册静态注册2.2 master driver3.input子系统4.pwm4.1 sysfs接口操作pwm4.2 高精度定时器模拟软件pwm5.iio6.dma6.1 为dma client driver提供api6.2 uart dma6.3 spi dma1.协议I2C读时序i2cget所有都是scl高时sda才生效一个主机可控制多个从机使用7位地址最多可使用128个唯一地址如下发送数据是读出的值。i2c没有像spi片选线主机将从机地址发给每个从机比较寻址匹配则向主机发送低电平ACK不匹配则SDA保持高。如下A表示ACK逻辑分析仪一个接SDA一个接SCL一个接地。I2C写时序i2cset1.1 i2ctoolsSMBus是I2C的一个子集在 I2C 上加了电压、时钟、超时、ACK、数据格式等限制更适合系统管理场景i2ctools提供i2c_smbus_read_block_data(这函数底层会加pec前提是如下设备开启了pec如果pec错了则该函数返回错误)。// 手动计算一次 SMBus 读操作的 PEC 校验值。PEC 本质就是 CRC-8typedefunsignedshortu16;typedefunsignedintu32;typedefunsignedcharu8;#definePOLY(0x1070U3)// CRC-8 的计算规则staticu8crc8(u16 data)// 算一个字节{inti;for(i0;i8;i){// CRC 是按 bit 算的。一个字节有 8 bitif(data0x8000)// 判断当前最高位是不是 1datadata^POLY;datadata1;// 左移一位处理下一个 bit}return(u8)(data8);// 算完 8 个 bit 后把高 8 位取出来作为新的 CRC 值}staticu8i2c_smbus_pec(u8 crc,u8*p,size_tcount){inti;for(i0;icount;i)// 逐字节处理crccrc8((crc^p[i])8);returncrc;}intmain(){u8 pec;charbuf[12]{0xcc};// cc是8位即 高7位addr0x66 低1位写0charbuf0[12]{0xcd};// cd是8位即 高7位addr0x66 低1位读1charbuf1[12]{0x88};// 读的寄存器地址0x88charbuf2[12]{0xc2,0xe0};// 读出的数据// S - 0x66W - A - 0x88 - A - Sr - 0x66R - A - 0xc2 - A - 0xe0 - N - PEC - Ppeci2c_smbus_pec(0,buf,1);peci2c_smbus_pec(pec,buf1,1);// 这里不是buf0因为要按照上面顺序peci2c_smbus_pec(pec,buf0,1);peci2c_smbus_pec(pec,buf2,2);printf(test0x%x\n,pec);// 正常解析前5个字节的crc8打印出0x3dreturn0;// 主机想要的是0xc2 0xe0但是由于干扰导致某个bit错了主机采样成0xc2 0xf0没有pec主机就信了有pec后从设备最后发一个0x3d校验字节主机收到数据后也自己算一次如果不是0x3d丢弃并retry。}--地址被探测了但没有芯片应答。 git://git.kernel.org/pub/scm/utils/i2c-tools/i2c-tools.git。UU探测被跳过了不能确定有没有芯片这个地址目前正被驱动程序使用驱动里配置了这个地址0x14。37在0x37这个地址找到了芯片。如下-fforce -yyesi2cdetect -y -q 5i2cdump。i2ctransfer支持16位/32位寄存器的读写w后面数字是设备/slave/器件即0x50后面的字节数2.模型每次往client或driver链表里添加都会与对方匹配调用match// i2c dev driver : i2c-dev.c :open(/dev/i2c-0)选择 I2C 控制器(使用open函数通过minor找到adapter分配client配置client的adapter然后把client放入file中)ioctl(I2C_SLAVE,address)选择 I2C 从设备地址ioctl(I2C_RDWR/I2C_SMBUS,...)真正传输数据(根据I2C_RDWR或者I2C_SMBUS来调用不同的i2c传输函数发送和接受数据给i2c_adapter_driver)2.1 I2C设备添加用户空间注册roots430:/sys/bus/i2c/devices/i2c-89# echo 0x50 delete_device-sh: echo:writeerror: No suchfileor directory# delete_device删除设备只能删除在用户空间创建的i2c设备dts里注册的不能删rootbmc:/sys/bus/i2c/devices/i2c-0# echo 0xd delete_device // 删除bus0上0x0d设备里面0-000d文件夹被删除这时i2cdetect -y看设备在不在rootbmc:/sys/bus/i2c/devices/i2c-0# rmmod basecpld //驱动这行lsmod查看不到如果这行比上行先执行0-000d文件夹还在但里面节点没了rootbmc:/sys/bus/i2c/devices/i2c-0# insmod ~/basecpld.ko // 驱动rootbmc:/sys/bus/i2c/devices/i2c-0# echo basecpld 0xd new_device // 设备将ko放到对应路径/lib/modules/4.1.51/extra/basecpld.ko这行执行后不用上行insmod静态注册1.没有dtsarm架构board-xxx-yyy.c架构板级文件或x86架构下xxx-yyy-init-zzz.c初始化文件。staticstructi2c_board_infomy_tmp75_info{I2C_BOARD_INFO(my_tmp75,0x48),// 设备名设备地址};// infoi2c设备信息就是上面的结构体// ninfo中有几个设备// 如下函数在linux-aspeed/drivers/i2c/i2c-boardinfo.ci2c_register_board_info(intbusnum,structi2c_board_infoconst*info,unsignedn){devinfo-busnumbusnum;// busnum哪一条总线也就是选择哪一个i2c控制器adapterdevinfo-board_info*info;list_add_tail(devinfo-list,__i2c_board_list);/* 将上面定义的i2c设备信息添加进__i2c_board_list链表中 */}2.出现了dts之后将这些的板级信息全部都定义在设备树arch/arm/boots/dtslinux在启动uboot的时会自动展开dts上的硬件信息自动调用i2c_register_board_info函数将设备注册进设备链表中i2c总线相对于芯片的cpu而言也是设备。// 如上2种方式将硬件设备添加到设备链表之后如下内核是怎么向i2c总线动态注册这些设备的// I2C controller driver probe// v// i2c_add_adapter(adap结构体)// |-- 创建 /dev/i2c-0// | 由 i2c-dev.c 监听 adapter 注册后创建// |-- 解析设备树子节点// tmp7548 - 创建真实 i2c_client 0-00483.i2c_new_device不管i2c设备是否真的存在都实例化i2c_clienti2c_new_probed_device调用probe函数去探测i2c地址是否有ACK存在则实例化i2c_client。staticstructi2c_board_infosfe4001_hwmon_info{I2C_BOARD_INFO(max6647,0x4e),};intsfe4001_init(structefx_nic*efx){// 不需要知道busnum网卡驱动初始化时已经获得了自己的 I2C 控制器efx-board_info.hwmon_clienti2c_new_device(efx-i2c_adap,sfe4001_hwmon_info);//使用i2c_adapter创建i2c_client}2.2 master driver/{i2c-bus-virtual{// 创建一个platform devicecompatible100ask,i2c-bus-virtual;//使用字符串匹配i2c_adpter_drv};};// i2c_adapter_drv.cstaticstructi2c_adapter*g_adapter;staticunsignedchareeprom_buffer[512];// 模拟一个 512 字节 EEPROMstaticinteeprom_cur_addr0;// 当前读写地址staticvoideeprom_emulate_xfer(structi2c_adapter*i2c_adap,structi2c_msg*msg){inti;if(msg-flagsI2C_M_RD){// 读for(i0;imsg-len;i){msg-buf[i]eeprom_buffer[eeprom_cur_addr];if(eeprom_cur_addr512)eeprom_cur_addr0;}}else{// i2cset -f -y 4 0x50 0 0x55 i2c-tools调用ioctl(fd,I2C_RDWR)进入i2c-dev.c进行下面赋值// msg.addr 0x50;// msg.flags 0; // 写// msg.len 2;// msg.buf[0] 0x00; // 寄存器地址// msg.buf[1] 0x55; // 要写的数据if(msg-len1){// 如上进入eeprom_cur_addrmsg-buf[0];for(i1;imsg-len;i){eeprom_buffer[eeprom_cur_addr]msg-buf[i];// eeprom_buffer[0] 0x55;if(eeprom_cur_addr512)eeprom_cur_addr0;}}}}staticinti2c_bus_virtual_xfer(structi2c_adapter*i2c_adap,structi2c_msgmsgs[],intnum)// i2cget/i2cset/i2cdetect 都会进去该函数{inti;for(i0;inum;i){if(msgs[i].addr0x50){// 所以 i2cdetect 扫描时只有 0x50 有响应// 50: 50 -- -- -- -- ...其他地址失败所以显示 --eeprom_emulate_xfer(i2c_adap,msgs[i]);}else{i-EIO;break;}}returni;}// 如下i2cdetect -F 4// Functionalities implemented by /dev/i2c-4:// I2C yes// SMBus Quick Command yes// SMBus Send Byte yes ....staticu32i2c_bus_virtual_func(structi2c_adapter*adap){return(I2C_FUNC_I2C|I2C_FUNC_NOSTART|I2C_FUNC_SMBUS_EMUL|I2C_FUNC_SMBUS_READ_BLOCK_DATA|I2C_FUNC_SMBUS_BLOCK_PROC_CALL|I2C_FUNC_PROTOCOL_MANGLING);}conststructi2c_algorithmi2c_bus_virtual_algo{.master_xferi2c_bus_virtual_xfer,.functionalityi2c_bus_virtual_func,};staticinti2c_bus_virtual_probe(structplatform_device*pdev){g_adapterkzalloc(sizeof(*g_adapter),GFP_KERNEL);// 创建i2c_adapterg_adapter-ownerTHIS_MODULE;g_adapter-classI2C_CLASS_HWMON|I2C_CLASS_SPD;g_adapter-nr-1;// 让内核动态分配 I2C bus 编号snprintf(g_adapter-name,sizeof(g_adapter-name),i2c-bus-virtual);g_adapter-algoi2c_bus_virtual_algo;i2c_add_adapter(g_adapter);// 注册i2c_adapterreturn0;}///////////////////////////////////////////////////////////////////////////////////staticinti2c_bus_virtual_remove(structplatform_device*dev){i2c_del_adapter(g_adapter);return0;}staticconststructof_device_idi2c_bus_virtual_ids[]{{.compatible100ask,i2c-bus-virtual,},{/* end of list */}};structplatform_driveri2c_bus_virtual_driver{.driver{.namei2c-bus-virtual,.of_match_tablei2c_bus_virtual_ids,},.probei2c_bus_virtual_probe,.removei2c_bus_virtual_remove,};staticinti2c_bus_virtual_init(void){intretplatform_driver_register(i2c_bus_virtual_driver);// 把i2c_bus_virtual_driver注册进platform总线returnret;}staticvoidi2c_bus_virtual_exit(void){platform_driver_unregister(i2c_bus_virtual_driver);}module_init(i2c_bus_virtual_init);module_exit(i2c_bus_virtual_exit);...// 复制dtb文件到tftp目录cp arch/arm/boot/dts/imx6ull-14x14-ebf.dtb~/tftpboot/// 复制ko文件到文件目录// insmod i2c_adapter_drv.ko (先移进来)// i2cdetect -y -a -li2c-4i2c i2c-bus-virtual I2C adapteri2c挂死如clock线一直被拉低i2cdetect扫出来很慢且没有设备量波形发给厂商一般换设备FW。一般954x的devdriver probe后不需要手动切i2ctransfer -f -y 6 w10x70 0x10如果954x切不动一般后面设备挂死。cpu要上电dmesg | grep probe有/sys/bus/i2c/devices/6710-0074但是i2cdetect -y 6710没有0x74。接了一个gpu卡gpu升温快最长3秒获取温度给风扇调速进程pid算法算风扇转速如果温度获取慢影响散热策略接了一张raid卡后面接了几十块硬盘数据交互量非常大i2c一个字节一个字节传输慢要获取每个硬盘温度如果把raid卡数据访问完肯定超过3秒再去访问gpugpu肯定超温了常见解决方法给gpu单独开一个线程和其他进程争抢访问i2c bus锁。3.input子系统cat/proc/bus/input/devices# 查看具体设备,包含上面event0...I:Bus0011Vendor0001Product0001Versionab41: 这一行显示了设备的总线类型、供应商 ID、产品 ID 和固件版本。在这个例子中该设备的总线类型为 0011供应商 ID 为 0001 产品 ID 为 0001固件版本为 ab41。 N:NameAT Translated Set 2 keyboard:这一行显示了设备的名称。在这个例子中该设备的名称为AT Translated Set 2 keyboard。 P:Physisa0060/serio0/input0: 这一行显示了设备的物理位置。在这个例子中该设备的物理位置是 isa0060/serio0/input0。 S:Sysfs/devices/platform/i8042/serio0/input/input1: 这一行显示了设备在 sysfs 文件系统中的路径。在这个例子中该设备的路径是/devices/platform/i8042/serio0/input/input1。 U:Uniq: 这一行显示了设备的唯一标识符。在这个例子中唯一标识符为空。 H:Handlerssysrq kbd event1 leds: 这一行显示了设备的处理程序。它指示了处理设备输入事件的程序或模块。在这个例子中设备有 sysrq、kbd、event1 和 leds 这些处理程序其中 event1 就表示设备节点为/dev/input/event1。 B:PROP0: 这一行显示了设备的属性。在这个例子中设备的属性值为0。 B:EV120013: 这一行显示了设备支持的事件类型。在这个例子中设备支持 EV_SYN、 EV_KEY、EV_MSC 和 EV_LED 这些事件类型。 B:KEY4020000003803078f800d001 feffffdfffefffff fffffffffffffffe: 这一行显示了设备支持的按键。每个按键对应一个位1 表示按键被按下0 表示按键未被按下。该行显示了按键的状态以16进制表示。 B:MSC10: 这一行显示了设备支持的杂项事件。在这个例子中设备支持 MSC_SCAN事件。 B:LED7: 这一行显示了设备支持的 LED 灯。在这个例子中设备支持3个 LED 灯使用一个7位的二进制数表示灯的状态。 struct input_dev{unsigned long evbit:事件分类# define EV_KEY 0x01 键盘......keybit:支持的键值# define KEY_1 2 如短按是KEY_1 长按3s是KEY_2...在中断子系统中判断按键时间再input子系统给input_dev赋值填充最后调input_event完成事件发送}中断函数里要用如下结构体第一行读取电平第二行上报按键。第三行获取设备资源第四行申请中断。如果用全局变量多个按键会覆盖。按下按键进入到中断程序里黄色是上报按键事件到用户空间。最重要的probe函数中创建i_dev并填充并赋给私有数据button_data如上从设备树button-gpios拿到gpio获取中断号并注册中断。如下是应用循环读监控事件发生。input_report_key会填充struct input_event eventevent.type(对应evbit)EV_KEY;event.code(对应keybit)BTN_0;event.value1;read(hexdump /dev/input/eventX:十六进制就是input_event结构体成员值)▲ evdev(Handler有很多类似dev dirver)evdev.c(类似spidev.c/i2c-dev.c): evdev_connect()创建/dev/input/event4 ▲ Input Core(drivers/input/input.c)当input_register_device(i_dev)时将input_dev加入input_device_list然后遍历input_handler_list看谁可以处理它根据EV_KEY找到上面evdev ▲ input_dev4.pwmtach风扇给bmc。pwmbmc给风扇。参考【bmc4】ltpi。// 占空比0.5ms、周期20ms// 0.5ms————0度;// 1.0ms————45度;// 1.5ms————90度;// 2.0ms————135度;// 2.5ms————180度;/{hc_sg90{compatiblehc-sg90;pwmspwm9引用PWM controller lable020000000周期20ms0上面polarity;statusokay;};};staticstructpwm_device*pwm_test;staticintsg90_probe(structplatform_device*pdev){structdevice_node*nodepdev-dev.of_node;// 获取设备树节点hc_sg90{...}因为已经compatible匹配到pwm_testdevm_of_pwm_get(pdev-dev,node,NULL);// 从子节点中获取pwmsif(IS_ERR(pwm_test)){return-1;}pwm_config(pwm_test,1500000,20000000);/* 配置PWMduty 1.5ms90度周期20000000ns 20ms */pwm_set_polarity(pwm_test,PWM_POLARITY_NORMAL);/* 设置输出极性占空比duty为高电平 */pwm_enable(pwm_test);/* 使能PWM输出 */return0;}staticssize_tsg90_write(structfile*filp,constchar__user*buf,size_tsize,loff_t*offset){unsignedchardata[1];if(size!1)return1;intrescopy_from_user(data,buf,size);pwm_config(pwm_test,data[0],20000000);// data[0]同上return1;}...4.1 sysfs接口操作pwmcat /sys/kernel/debug/pwmpwmchip1类似gpiochip1里有很多pinnpwm是该组控制器支持的pin个数period周期。rockchip_pwm_probe()▼pwmchip_add()▼pwmchip_sysfs_export()▼device_create(pwmchip0)▼/sys/class/pwm/pwmchip0 ┌─────────┴─────────┐ export unexport │ echo0export ▼export_store()▼pwm_export_child()▼device_register(pwm0)▼/sys/class/pwm/pwmchip0/pwm0 ┌──────┼─────────────┬────────────┐ │ │ │ │ period duty_cycle enable polarity...│ echo20000000period ▼period_store()▼pwm_apply_state()▼ chip-ops-apply()▼rockchip_pwm_apply()▼writel()▼ PWM硬件寄存器 ▼ PWM输出发生变化4.2 高精度定时器模拟软件pwmleds{compatiblepwm-leds;led-gpiosgpio0 RK_PB7 GPIO_ACTIVE_HIGH;};hrtimer 就像一个每1ms 响一次的闹钟闹钟响一次就执行一次回调函数回调函数里根据计数值决定 GPIO 是拉高还是拉低。这样反复执行就用普通 GPIO 模拟出了 PWM 用户写/dev/pwm-gpio ▼cdev_test_write()▼ 修改 sum_count周期/high_count高电平 ▼ hrtimer 每1ms 触发一次 ▼pwm_timer_func()▼ 根据 timer_count 判断拉高还是拉低 GPIO ▼ GPIO RK_PB7 输出近似 PWM 波形5.iioiio是专用于模数转换器ADC和数模转换器DAC的内核子系统.。fori in/sys/class/hwmon/hwmon*/name;doecho-n${i%/*}: ;cat$i2/dev/null||echoN/A;done;CONFIG_SENSORS_IIO_HWMON Trigger ↓ ADC采样 ↓ Buffer缓存 ↓ 应用程序读取Buffer1.sysfs Trigger软件触发 echo2/sys/bus/iio/devices/iio_sysfs_trigger/add_trigger/sys/bus/iio/devices/trigger2/里有 namesysfstrig2 和 trigger_now echo sysfstrig2/sys/bus/iio/devices/iio:device0/trigger/current_trigger// 绑定也可以指向下面irqtrig85echo1trigger_now// 软件模拟了一次中断触发一次采样2.IRQ Trigger 如按键按下触发设备树中interrupts30...gpio30触发中断采样3.hrtimer Trigger定时器触发 openbmc不会去读../iio:device0/..,只认/sys/class/hwmon/..通过dts iio-hwmon联系起来6.dma6.1 为dma client driver提供api你的公司Client Driver 一家需要频繁运输货物的“SPI设备制造商”。 物流公司DMA Controller 瑞芯微物流RK3568 DMA。 运输车队DMA Channel 物流公司旗下的一个车队。 货物Data 需要传输的数据。 Client DriverSPI/I2S/UART... │ │dma_request_chan()▼ DMAEngine Framework │ │ 找到对应 DMA Controller Driver ▼ DMA Controller Driver │ │ 配置 DMA Controller ▼ DMA硬件6.2 uart dma用户空间 │ write()/read() │ tty/serial_core.c │ 8250_core.c │ 8250_dma.c │ DMAEngine Framework │ dw_dmac (DMA Controller) │ UART FIFOserial_core.c负责TTY层↓8250_core.c负责8250串口↓8250_dma.c负责DMA发送接收↓DMA Engine↓DMA Controller Driver因此UART Driver 不直接控制 DMA 控制器。6.3 spi dma