I2C调试指南
前言
S100芯片提供了标准的I2C总线,I2C总线控制器通过串行数据线(SDA)和串行时钟(SCL)线在连接到总线的器件间传递信息。每个器件都有一个唯一的地址(无论是微控制器——MCU、LCD控制器、存储器或键盘接口),而且都可以作为一个发送器和一个接收器(由器件的功能决定)。
I2C控制器支持以下功能:
- 支持四种速度模式:
- standard mode(0-100Kb/s)
- fast mode(100-400Kb/s)
- fast mode plus(400-1000Kb/s)
- high-speed mode(1000Kb/s-3.4Mb/s)
- 支持主从模式配置
- 支持7位和10位寻址模式
驱动代码
drivers/i2c/i2c-dev.c # I2C字符设备接口代码
drivers/i2c/i2c-core-base.c # I2C框架代码
drivers/i2c/busses/i2c-designware-platdrv.c # I2C驱动代码源文件
内核配置位置
/* hobot-drivers/configs/drobot_s100_defconfig */
CONFIG_I2C_CHARDEV=y # I2C驱动应用层配置宏
CONFIG_I2C_DESIGNWARE_PLATFORM=y # DW I2C驱动配置宏
内核DTS节点配置
/*kernel/arch/arm64/boot/dts/hobot/drobot-s100-soc.dtsi*/
i2c0: i2c@39420000 {
power-domains = <&scmi_smc_pd PD_IDX_LSPERI_TOP>;
#address-cells = <1>;
#size-cells = <0>;
compatible = "snps,designware-i2c";
reg = <0x0 0x39420000 0x0 0x10000>;
clocks = <&scmi_smc_clk CLK_IDX_TOP_PERI_I2C0>;
clock-names = "apb_pclk";
interrupts = <GIC_SPI PERISYS_I2C0_INTR PERISYS_I2C0_INTR_TRIG_TYPE>;
clock-frequency = <400000>;
i2c-sda-hold-time-ns = <50>;
pinctrl-names = "default", "gpio";
pinctrl-0 = <&cam_i2c0>;
pinctrl-1 = <&cam_i2c0_gpio>;
scl-gpios = <&cam_port0 8 GPIO_ACTIVE_HIGH>;
sda-gpios = <&cam_port0 9 GPIO_ACTIVE_HIGH>;
status = "okay";
};
I2C使用
对于I2C的使用说明在kernel/Documentation/i2c目录下有详细的说明,本文主要列出S100 I2C驱动接口特殊的部分。
Kernel Space
S100 I2C驱动在Kernel Space下提供了可以设置I2C传输频率的接口,使用方法如下:
I2C速度配置
默认的I2C速率为400K,支持100k/400k/1M/3.4M四种速率,可通过修改dts中相应i2c节点的clock-frequency完成速率修改。对应到代码中有关实际速率选择代码如下:
kernel/drivers/i2c/busses/i2c-designware-common.c
I2C支持的速率配置如下:
static const u32 supported_speeds[] = {
I2C_MAX_HIGH_SPEED_MODE_FREQ,
I2C_MAX_FAST_MODE_PLUS_FREQ,
I2C_MAX_FAST_MODE_FREQ,
I2C_MAX_STANDARD_MODE_FREQ,
};
...
kernel/drivers/i2c/busses/i2c-designware-pcidrv.c
获取设备树中clock-frequency的函数接口如下:
i2c_parse_fw_timings(&pdev->dev, t, false);
展开为:
void i2c_parse_fw_timings(struct device *dev, struct i2c_timings *t, bool use_defaults)
{
bool u = use_defaults;
u32 d;
i2c_parse_timing(dev, "clock-frequency", &t->bus_freq_hz,
I2C_MAX_STANDARD_MODE_FREQ, u);
d = t->bus_freq_hz <= I2C_MAX_STANDARD_MODE_FREQ ? 1000 :
t->bus_freq_hz <= I2C_MAX_FAST_MODE_FREQ ? 300 : 120;
i2c_parse_timing(dev, "i2c-scl-rising-time-ns", &t->scl_rise_ns, d, u);
d = t->bus_freq_hz <= I2C_MAX_FAST_MODE_FREQ ? 300 : 120;
i2c_parse_timing(dev, "i2c-scl-falling-time-ns", &t->scl_fall_ns, d, u);
i2c_parse_timing(dev, "i2c-scl-internal-delay-ns",
&t->scl_int_delay_ns, 0, u);
i2c_parse_timing(dev, "i2c-sda-falling-time-ns", &t->sda_fall_ns,
t->scl_fall_ns, u);
i2c_parse_timing(dev, "i2c-sda-hold-time-ns", &t->sda_hold_ns, 0, u);
i2c_parse_timing(dev, "i2c-digital-filter-width-ns",
&t->digital_filter_width_ns, 0, u);
i2c_parse_timing(dev, "i2c-analog-filter-cutoff-frequency",
&t->analog_filter_cutoff_freq_hz, 0, u);
}
验证I2C速率是否有效的函数接口如下:
i2c_dw_validate_speed(dev);
展开为:
int i2c_dw_validate_speed(struct dw_i2c_dev *dev)
{
struct i2c_timings *t = &dev->timings;
unsigned int i;
/*
* Only standard mode at 100kHz, fast mode at 400kHz,
* fast mode plus at 1MHz and high speed mode at 3.4MHz are supported.
*/
for (i = 0; i < ARRAY_SIZE(supported_speeds); i++) {
if (t->bus_freq_hz == supported_speeds[i])
return 0;
}
dev_err(dev->dev,
"%d Hz is unsupported, only 100kHz, 400kHz, 1MHz and 3.4MHz are supported\n",
t->bus_freq_hz);
return -EINVAL;
}
User Space
通常,I2C设备由内核驱动程序控制,但也可以从用户态访问总线上的所有设备,通过/dev/i2c-%d接口来访问,Kernel下面的Documentation/i2c/dev-interface.rst文档里有详细的介绍。