Firefly-RK3308开发板上的 AD 接口有两种
TS-ADC(Temperature Sensor):支持两通道,时钟频率必须低于800KHZ
SAR-ADC(Successive Approximation Register):支持六通道单端10位的SAR-ADC,时钟频率必须小于13MHZ。(常用)
内核采用工业 I/O 子系统来控制 ADC,该子系统主要为 AD 转换或者 DA 转换的传感器设计。 下面以 SAR-ADC 为例子,介绍 ADC 的基本配置方法。
SAR-ADC 的 DTS 节点在 kernel/arch/arm64/boot/dts/rockchip/rk3308.dtsi 文件中定义,如下所示:
saradc: saradc@ff1e0000 { compatible = "rockchip,rk3308-saradc", "rockchip,rk3399-saradc"; reg = <0x0 0xff1e0000 0x0 0x100>; interrupts = <GIC_SPI 37 IRQ_TYPE_LEVEL_HIGH>; #io-channel-cells = <1>; clocks = <&cru SCLK_SARADC>, <&cru PCLK_SARADC>; clock-names = "saradc", "apb_pclk"; resets = <&cru SRST_SARADC_P>; reset-names = "saradc-apb"; status = "disabled"; };想要使用CPU上的ADC资源,必须要添加DTS配置及设备树,所以用户首先需在 DTS 文件中添加 ADC 的资源描述: 这里我举2个例子 (1)adc模拟的按键驱动(RK适配的) (2)萤火虫firefly适配的adc使用demo 接下来看下他们分别的设备树如何描述 (1)adc模拟的按键(简单逻辑根据adc检测的电压的不同,转换成数字信号范围去检测按了哪个按键)
adc-keys { compatible = "adc-keys"; io-channels = <&saradc 1>;//这里申请的是 SARADC 通道1。 io-channel-names = "buttons"; poll-interval = <100>; keyup-threshold-microvolt = <1800000>; esc-key { linux,code = <KEY_MICMUTE>; label = "micmute"; press-threshold-microvolt = <1130000>; }; home-key { linux,code = <KEY_MODE>; label = "mode"; press-threshold-microvolt = <901000>; }; menu-key { linux,code = <KEY_PLAY>; label = "play"; press-threshold-microvolt = <624000>; }; vol-down-key { linux,code = <KEY_VOLUMEDOWN>; label = "volume down"; press-threshold-microvolt = <300000>; }; vol-up-key { linux,code = <KEY_VOLUMEUP>; label = "volume up"; press-threshold-microvolt = <18000>; }; };(2)adc-demo
adc_demo: adc_demo{ status = "disabled"; compatible = "firefly,rk3399-adc"; io-channels = <&saradc 3>;//这里申请的是 SARADC 通道3。 };(1)adc按键驱动下载链接 (2)点击adc使用demo驱动下载
用户驱动可参考 RK adc Key驱动 :这是一个侦测 模拟出的5个按键状态的驱动。首先在驱动文件中定义 of_device_id 结构体数组:
static const struct of_device_id adc_keys_of_match[] = { { .compatible = "adc-keys", }, { } };然后将该结构体数组填充到要使用 ADC 的 platform_driver 中:
static struct platform_driver __refdata adc_keys_driver = { .driver = { .name = "adc_keys", .of_match_table = of_match_ptr(adc_keys_of_match), }, .probe = adc_keys_probe, }; module_platform_driver(adc_keys_driver);接着在 probe函数 中对 DTS 所添加的资源进行解析: 简单逻辑是:获取道设备树描述的节点,遍历节点(有多少个节点都会读取到),在获取节点中相应的属性,分别保存起来。 例句相关部分驱动:(具体可以下载看) ADC相关函数:
st = devm_kzalloc(dev, sizeof(*st), GFP_KERNEL);//申请内核空间内核 if (!st) return -ENOMEM; st->channel = devm_iio_channel_get(dev, "buttons");//获取设备IIO通道 与str匹配 if (IS_ERR(st->channel)) return PTR_ERR(st->channel); if (!st->channel->indio_dev) return -ENXIO; error = iio_get_channel_type(st->channel, &type);//获取io通道类型 if (error < 0) return error; if (type != IIO_VOLTAGE) { dev_err(dev, "Incompatible channel type %d\n", type); return -EINVAL; }读取设备树中的属性
device_for_each_child_node(dev, child) {//循环去读子节点按键 if (fwnode_property_read_u32(child, "press-threshold-microvolt",//读取dtsi中子节点中press-threshold-microvolt属性 &map[i].voltage)) { dev_err(dev, "Key with invalid or missing voltage\n"); fwnode_handle_put(child); return -EINVAL; } map[i].voltage /= 1000; if (fwnode_property_read_u32(child, "linux,code",//读取dtsi中子节点中linux,code属性 &map[i].keycode)) { dev_err(dev, "Key with invalid or missing linux,code\n"); fwnode_handle_put(child); return -EINVAL; } i++; }ADC驱动用到的函数比较简单。
为何按上面的步骤申请 SARADC,会出现申请报错的情况? 驱动需要获取ADC通道来使用时,需要对驱动的加载时间进行控制,必须要在saradc初始化之后。saradc是使用module_platform_driver()进行平台设备驱动注册,最终调用的是module_init()。所以用户的驱动加载函数只需使用比module_init()优先级低的,例如:late_initcall(),就能保证驱动的加载的时间比saradc初始化时间晚,可避免出错。