WK2124 SPI转串口驱动移植避坑指南:在SC806开发板上调试485功能的那些事儿
WK2124 SPI转串口驱动移植实战:从设备树配置到485功能调试全解析
在嵌入式Linux开发中,外设接口扩展是常见需求。当项目需要多路串口而主控资源不足时,SPI转串口芯片如WK2124成为理想选择。本文将基于SC806开发板(MSM8909平台),深入讲解WK2124驱动移植的全过程,重点解决485功能集成中的典型问题。
1. 硬件架构与设计考量
WK2124作为一款SPI转4路串口的专用芯片,其硬件连接需要特别注意几个关键信号:
- SPI总线:包含CS(片选)、CLK(时钟)、MOSI(主机输出从机输入)、MISO(主机输入从机输出)四线
- 中断信号(IRQ):需连接至支持外部中断的GPIO,通常需要上拉电阻
- 复位信号(RST):可采用阻容复位电路或由主控GPIO控制
- 485方向控制:每路485接口需要一个独立的GPIO控制收发状态
在SC806开发板上,我们观察到以下硬件配置:
SPI总线:GPIO0-3复用为SPI3 复位引脚:GPIO89 中断引脚:GPIO92 485控制:GPIO97(通道1)、GPIO69(通道2)硬件设计中容易忽略的要点包括:
- SPI的CS信号不能持续拉低,必须由主控动态控制
- IRQ引脚需要10KΩ上拉电阻确保稳定
- 485方向控制GPIO的响应速度直接影响通信质量
2. 设备树配置深度解析
设备树是Linux内核识别硬件的关键。对于WK2124移植,需要完成SPI总线定义和设备节点挂载两大部分。
2.1 SPI总线配置
SC806开发板的SPI3总线需要通过GPIO复用实现。在msm8909.dtsi中添加:
spi_3: spi@78b7000 { compatible = "qcom,spi-qup-v2"; reg = <0x78b7000 0x600>, <0x7884000 0x23000>; interrupts = <0 97 0>, <0 238 0>; spi-max-frequency = <19200000>; pinctrl-names = "spi_default", "spi_sleep"; pinctrl-0 = <&spi3_default &spi3_cs0_active>; pinctrl-1 = <&spi3_sleep &spi3_cs0_sleep>; clocks = <&clock_gcc clk_gcc_blsp1_ahb_clk>, <&clock_gcc clk_gcc_blsp1_qup3_spi_apps_clk>; clock-names = "iface_clk", "core_clk"; qcom,master-id = <86>; };关键参数说明:
| 参数 | 说明 | 典型值 |
|---|---|---|
| reg | SPI控制器物理地址和BAM地址 | 需参考芯片手册 |
| interrupts | SPI中断和BAM中断号 | 平台相关 |
| spi-max-frequency | SPI最大时钟频率 | ≤20MHz |
| pinctrl-0/1 | 默认和睡眠状态下的引脚配置 | 必须与硬件一致 |
2.2 WK2124设备节点
在SPI3总线下的WK2124设备节点配置:
&spi_3 { status = "okay"; wk2xxx_spi@00 { compatible = "qcom,wk2xxx_spi"; reg = <0>; spi-max-frequency = <10000000>; reset_gpio = <&msm_gpio 89 1>; irq_gpio = <&msm_gpio 92 0>; rs485ctl1-gpio = <&msm_gpio 97 1>; rs485ctl2-gpio = <&msm_gpio 69 1>; }; };常见配置错误包括:
compatible值与驱动不匹配- SPI频率超过芯片规格(10MHz)
- GPIO编号与原理图不符
- 遗漏reset_gpio或irq_gpio配置
3. 驱动移植与485功能实现
3.1 驱动框架分析
WK2124驱动主要包含以下组件:
- SPI设备驱动:实现与芯片的底层通信
- UART驱动:向上层提供4个标准串口设备
- 中断处理:处理数据传输和状态变化
驱动工作流程:
graph TD A[用户空间write] --> B[tty核心层] B --> C[WK2124驱动tx_chars] C --> D[SPI传输] D --> E[硬件FIFO] E --> F[485方向控制]3.2 485方向控制实现
485通信需要精确控制收发状态切换。在驱动中,我们需要:
- 在probe函数中解析设备树的485控制GPIO:
static int wk2xxx_spi_rs485ctl_parse_dt(struct device *dev, int *gpio_num) { *gpio_num = of_get_named_gpio(dev->of_node, "rs485ctl1-gpio", 0); if (!gpio_is_valid(*gpio_num)) { dev_err(dev, "invalid gpio number\n"); return -EINVAL; } if (gpio_request(*gpio_num, "rs485-ctl")) { dev_err(dev, "gpio request failed\n"); return -EBUSY; } gpio_direction_output(*gpio_num, 0); // 默认接收模式 return 0; }- 在发送数据前后控制GPIO状态:
static void wk2xxx_tx_chars(struct uart_port *port) { /* 开始发送前设置为发送模式 */ gpio_set_value(rs485ctl_gpio, 1); /* 实际数据发送代码... */ /* 发送完成后切换回接收模式 */ while (!tx_fifo_empty()) { udelay(10); } gpio_set_value(rs485ctl_gpio, 0); }关键时序考虑:
- 发送前至少提前1μs拉高GPIO
- 发送完成后等待FIFO真正清空再切换模式
- 对于高速通信,需要优化GPIO操作延迟
4. 调试技巧与问题排查
4.1 常见问题清单
| 现象 | 可能原因 | 排查方法 |
|---|---|---|
| SPI通信失败 | CS信号异常 | 用逻辑分析仪抓取SPI波形 |
| 中断不触发 | GPIO配置错误 | 检查设备树和引脚复用 |
| 485通信异常 | 方向控制时序问题 | 调整GPIO切换时机 |
| 数据丢失 | FIFO设置不当 | 调整触发阈值和超时时间 |
4.2 调试手段
- 内核打印:在关键路径添加printk
printk(KERN_DEBUG "TX start, FIFO status: 0x%02x\n", reg_val);- sysfs调试接口:通过sysfs暴露内部状态
static ssize_t show_debug(struct device *dev, struct device_attribute *attr, char *buf) { return sprintf(buf, "GPIO state: %d\n", gpio_get_value(gpio_num)); }示波器测量:验证关键信号时序
- CS信号有效宽度
- 485方向切换时机
- 中断信号边沿
性能分析:使用ftrace跟踪中断延迟
echo function_graph > /sys/kernel/debug/tracing/current_tracer echo wk2xxx_irq > /sys/kernel/debug/tracing/set_ftrace_filter cat /sys/kernel/debug/tracing/trace_pipe5. 高级优化技巧
5.1 DMA传输配置
对于高波特率应用,启用DMA可降低CPU负载:
static int wk2xxx_probe(struct spi_device *spi) { spi->bits_per_word = 8; spi->mode = SPI_MODE_0; if (spi_setup(spi)) { dev_err(&spi->dev, "SPI setup failed\n"); return -EIO; } spi->controller->dma_tx = dma_request_chan(&spi->dev, "tx"); spi->controller->dma_rx = dma_request_chan(&spi->dev, "rx"); }5.2 功耗管理
实现电源管理回调可优化能耗:
static int wk2xxx_suspend(struct device *dev) { struct spi_device *spi = to_spi_device(dev); struct wk2xxx_port *s = spi_get_drvdata(spi); disable_irq(spi->irq); gpio_set_value(s->reset_gpio, 0); return 0; } static const struct dev_pm_ops wk2xxx_pm_ops = { .suspend = wk2xxx_suspend, .resume = wk2xxx_resume, };5.3 多实例支持
通过动态分配资源支持多个WK2124芯片:
static int wk2xxx_probe(struct spi_device *spi) { struct wk2xxx_port *s; s = devm_kzalloc(&spi->dev, sizeof(*s), GFP_KERNEL); spi_set_drvdata(spi, s); /* 每个实例独立的中断和GPIO资源 */ s->irq = spi->irq; s->reset_gpio = of_get_gpio(spi->dev.of_node, 0); }6. 实战经验分享
在实际项目中,我们发现几个值得注意的细节:
- SPI时钟相位:WK2124对SPI模式0和模式3的支持存在差异,模式0通常更稳定
- 中断消抖:在电气环境复杂时,添加10-100μs的中断消抖可提高稳定性
- 485终端电阻:长距离通信时,需在总线两端添加120Ω终端电阻
- GPIO驱动能力:方向控制GPIO应有足够驱动能力(建议8mA以上)
一个典型的初始化检查清单:
- [ ] SPI总线时钟配置正确
- [ ] 设备树节点状态为"okay"
- [ ] 所有GPIO申请成功
- [ ] 中断处理函数注册无误
- [ ] 485方向控制初始化为接收模式
调试过程中,建议逐步验证:
- 首先确保SPI通信正常(可通过读取芯片ID验证)
- 然后测试中断功能
- 最后集成485方向控制
遇到问题时,可尝试以下步骤:
# 检查设备是否成功注册 ls /dev/ttyWK* # 查看内核消息 dmesg | grep wk2xxx # 验证GPIO状态 cat /sys/kernel/debug/gpio # 测试基本通信功能 stty -F /dev/ttyWK0 115200 echo "test" > /dev/ttyWK0