如何在DTS中正确描述SPI总线及双电能计量芯片配置?
ARM Linux SPI双电能计量芯片驱动实现方案
一、DTS总线与双设备配置
由于两颗芯片共享SPI总线、使用独立GPIO片选,需在设备树中明确描述SPI控制器及两个计量设备节点:
// 假设使用SPI1控制器,根据实际硬件调整节点名 &spi1 { pinctrl-names = "default"; // 绑定SPI控制器的pinctrl配置(包含mosi/miso/sck引脚复用) pinctrl-0 = <&spi1_pins>; status = "okay"; // 全局声明可用片选GPIO,每个设备可单独指定 cs-gpios = <&gpio1 10 GPIO_ACTIVE_LOW>, <&gpio1 11 GPIO_ACTIVE_LOW>; // 第一颗电能计量芯片 energy_meter@0 { // 与驱动的compatible字段匹配 compatible = "your-vendor,energy-meter-chip"; // reg值对应chip_select编号,从0开始 reg = <0>; spi-max-frequency = <1000000>; // 芯片支持的最高SPI时钟 // 单独指定该设备的片选GPIO(必须与硬件一致,不可修改) cs-gpios = <&gpio1 10 GPIO_ACTIVE_LOW>; }; // 第二颗电能计量芯片 energy_meter@1 { compatible = "your-vendor,energy-meter-chip"; reg = <1>; spi-max-frequency = <1000000>; cs-gpios = <&gpio1 11 GPIO_ACTIVE_LOW>; }; };
- SPI控制器的
pinctrl-0关联的引脚配置节点(如spi1_pins)需在板级DTS中定义,指定mosi/miso/sck引脚的复用功能与电气参数,这部分由SOC厂商的DTSI或板级配置完成,驱动无需修改。 - 每个设备节点的
cs-gpios必须严格匹配硬件连接的GPIO,内核会自动将该值填充到struct spi_device的cs_gpio字段。
二、struct spi_board_info字段说明
若使用设备树枚举设备(当前主流方式),无需手动初始化struct spi_board_info,内核会自动从DTS解析生成struct spi_device实例。
若需兼容无DTS的老内核场景:
bus_num:对应SPI控制器的编号(如SPI1对应1,需与内核中SPI控制器的注册编号一致)。chip_select:对应设备的片选编号,与DTS中reg值一致(第一颗为0,第二颗为1)。
三、单驱动管控双设备实现
驱动只需注册一个struct spi_driver,内核会为每个DTS中匹配的spi_device调用一次probe函数,通过私有数据区分不同设备:
#include <linux/spi/spi.h> #include <linux/device.h> // 私有数据结构,存储每个设备的独立状态与数据 struct energy_meter_data { struct spi_device *spi; int dev_id; // 区分两颗芯片(0或1) // 计量数据缓存 u32 voltage; u32 current; }; // sysfs电压属性读取函数 static ssize_t voltage_show(struct device *dev, struct device_attribute *attr, char *buf) { struct energy_meter_data *data = spi_get_drvdata(to_spi_device(dev)); // 此处添加从对应芯片读取电压的逻辑 return sprintf(buf, "%u\n", data->voltage); } static DEVICE_ATTR_RO(voltage); // sysfs电流属性读取函数 static ssize_t current_show(struct device *dev, struct device_attribute *attr, char *buf) { struct energy_meter_data *data = spi_get_drvdata(to_spi_device(dev)); // 此处添加从对应芯片读取电流的逻辑 return sprintf(buf, "%u\n", data->current); } static DEVICE_ATTR_RO(current); static struct attribute *energy_meter_attrs[] = { &dev_attr_voltage.attr, &dev_attr_current.attr, NULL, }; ATTRIBUTE_GROUPS(energy_meter); static int energy_meter_probe(struct spi_device *spi) { struct energy_meter_data *data; int ret; // 分配并初始化私有数据 data = devm_kzalloc(&spi->dev, sizeof(*data), GFP_KERNEL); if (!data) return -ENOMEM; data->spi = spi; data->dev_id = spi->chip_select; // 从spi_device获取设备编号 // 绑定私有数据到spi_device spi_set_drvdata(spi, data); // 注册sysfs属性组 ret = sysfs_create_group(&spi->dev.kobj, &energy_meter_group); if (ret) return ret; // 此处添加芯片初始化逻辑(如复位、配置寄存器) return 0; } static void energy_meter_remove(struct spi_device *spi) { sysfs_remove_group(&spi->dev.kobj, &energy_meter_group); } static const struct of_device_id energy_meter_of_match[] = { { .compatible = "your-vendor,energy-meter-chip" }, { /* Sentinel */ } }; MODULE_DEVICE_TABLE(of, energy_meter_of_match); static struct spi_driver energy_meter_driver = { .driver = { .name = "energy-meter-driver", .of_match_table = energy_meter_of_match, }, .probe = energy_meter_probe, .remove = energy_meter_remove, }; module_spi_driver(energy_meter_driver); MODULE_LICENSE("GPL"); MODULE_DESCRIPTION("Dual Energy Meter SPI Driver");
- 每个设备的sysfs属性会生成在各自的设备目录下(如
/sys/bus/spi/devices/spi1.0/voltage和/sys/bus/spi/devices/spi1.1/voltage),天然实现数据隔离。 - 芯片初始化、数据读取逻辑需根据芯片手册实现,读取时通过
spi_device的cs_gpio自动切换片选,无需手动控制GPIO。
四、SPI线路GPIO的指定说明
SPI的mosi/miso/sck引脚由pinctrl子系统管控,无需在驱动中指定:
- 在板级DTS中定义
spi1_pins节点,配置引脚复用为SPI功能、设置上下拉、速率等参数。 - SPI控制器节点通过
pinctrl-0引用该引脚配置节点,内核启动时会自动配置这些引脚。
内容的提问来源于stack exchange,提问作者Michael_Y
相关产品推荐
相关产品推荐

