AIO-PX30JD4 开发板上的 AD 接口有两种,分别为:温度传感器 (Temperature Sensor)、逐次逼近ADC (Successive Approximation Register)。其中:
TS-ADC(Temperature Sensor):支持两通道,时钟频率必须低于800KHZ
SAR-ADC(Successive Approximation Register):支持三通道单端10位的SAR-ADC,时钟频率必须小于13MHZ。
Linux内核采用工业 I/O 子系统(iio子系统)来控制 ADC,该子系统主要为 AD 转换或者 DA 转换而设计。本文以配置SAR-ADC为例,主要介绍 SAR-ADC的基本配置和使用的方法,其相关的数据结构,源码路径以及配置步骤如下:
该结构体主要是用于描述IO口所属的设备,其具体定义如下:
该结构体主要用于描述单个通道的属性,具体定义如下:
第一步:在AIO-PX30JD4的 DTS 文件:kernel/arch/arm64/boot/dts/rockchip/px30.dtsi中,添加saradc资源,应如下:
第二步:根据用户的通道需要选择对应的saradc通道,本次例程使用AIO-PX30-JD4上的ADC按键检测,选择通道2,配置如下:
io-channels 属性 为 选择的通道号,这里选择通道2
io-channel-names 属性 表示 为申请的通道起一个别名。
keyup-threshold-microvolt 属性 表示按键抬起,saradc通道2的电压(单位微伏)。
press-threshold-microvolt 属性 表示按键按下,saradc通道2的电压。
vol-up-key 在硬件连接上,为AIO-PX30-JD4 上的recovery 按键。
linux,code 属性 为 按键上报的键值,键值对应的动作 为 “音量+” 。
第一步: 在内核设备树添加saradc资源之后,可以在源码kernel/drivers/iio/adc/rockchip_saradc.c中添加对应的saradc数据结构体
第二步: 在rockchip_saradc_match[] 中,添加px30的compatible属性,使得saradc驱动可以匹配到saradc设备。因如下:
第三步: 填充saradc的platform_driver结构体:
第四步:通过module_platform_driver(rockchip_saradc_driver)宏平进行驱动的注册。
在设备上电的时候,内核会解析内核设备树,当检测到设备树上saradc的compatible属性与saradc驱动的of_device_id中的compatible成员一致的时候,便会调用rockchip_saradc.c中的rockchip_saradc_probe()函数来进行iio系统的adc设备的资源申请以及初始化(此处不再赘述,用户可自行查看源码)。
在进入系统后,会出现一个 /sys/bus/iio/devices/iio:device0的目录,表示创建成功。
第一步: 填充ADC按键驱动的adc_keys_of_match[]中的compatible成员用于匹配设备
第二步: 填充驱动结构体
第三步: 使用module_platform_driver(adc_keys_driver);往内核注册该驱动。
第四步: 设备树 compatible匹配正确,驱动注册成功之后,便会调用ADC按键驱动的adc_keys_probe()函数,进行输入子系统设备的注册(因为是按键驱动,所以使用输入子系统,此部分不在此讲述)与saradc的io通道的申请。
在进入系统后,通过 getevent 命令 :
其中我们可以看到:
这样表示我们的设备已经创建成功。
adc-keys.c驱动是通过输入子系统的轮询检测函数adc_keys_poll(),来不断地读取saradc通道的值,当不同按键按下的时候,是有不同的电压值的:
所以当recovery按键按下的时候,通过iio_read_channel_processed()函数获取到的电压值如果与设备树配置相符合的话,就会触发按键上报事件,而用户层会收到事件,从屏幕可以看到有 ”音量+“ 的动作。
有个便捷的方法可以在命令行中直接查询到每个SARADC的值:
其中in_voltage0_raw为通道0,in_voltage01_raw为通道1,以此类推。
以上面的例子为例,命令行输入 cat /sys/bus/iio/devices/iio:device0/in_voltage2_raw 来获取ADC电压转换后的数字量
注:iio_channel_get 通过 probe 函数传进来的参数 pdev 获取 IIO 通道结构体,probe 函数如下:
调用 iio_read_channel_raw 函数读取 AD 采集的原始数据并存入 val 中。
使用标准电压将 AD 转换的值转换为用户所需要的电压值。其计算公式如下:
注:
Vref 为标准电压
n 为 AD 转换的位数
Vresult 为用户所需要的采集电压
raw 为 AD 采集的原始数据
例如,标准电压为 1.8V,AD 采集位数为 10 位,AD 采集到的原始数据为 568,则:
功能:获取 iio 通道描述
参数:
dev: 使用该通道的设备描述指针
consumer_channel: 该设备所使用的 IIO 通道描述指针
功能:释放 iio_channel_get 函数获取到的通道
参数:
chan:要被释放的通道描述指针
功能:读取 chan 通道 AD 采集的原始数据。
参数:
chan:要读取的采集通道指针
val:存放读取结果的指针
驱动需要获取ADC通道来使用时,需要对驱动的加载时间进行控制,必须要在saradc初始化之后。saradc是使用module_platform_driver()进行平台设备驱动注册,最终调用的是module_init()。所以用户的驱动加载函数只需使用比module_init()优先级低的,例如:late_initcall(),就能保证驱动的加载的时间比saradc初始化时间晚,可避免出错。
GPIO, 全称 General-Purpose Input/Output(通用输入输出),是一种软件运行期间能够动态配置和控制的通用引脚。 PX30有4组GPIO bank:GPIO0~GPIO3,每组又以 A0~A7, B0~B7, C0~C7, D0~D7 作为编号区分(GPIO0在PD_PMU子系统中,GPIO1/GPIO2/GPIO3在PD_BUS子系统中)。 所有的GPIO在上电后的初始状态都是输入模式,可以通过软件设为上拉或下拉,也可以设置为中断脚,驱动强度都是可编程的。每个 GPIO 口除了通用输入输出功能外,还可能有其它复用功能,例如:
GPIO0_C2 可复用为 I2C1_SCL端口
GPIO0_C3 可复用为 I2C1_SDA端口
每个 GPIO 口的驱动电流、上下拉和重置后的初始状态都不尽相同,详细情况请参考《px30 规格书》中的 “Chapter 21 GPIO” 一章。 px30 的 GPIO 驱动是在以下 pinctrl 文件中实现的:
其核心是填充 GPIO bank 的方法和参数,并调用 gpiochip_add 注册到内核中。
本文以px30的gslx680外设(基于i2c通信的触摸屏)为例,讲述 gpio的输入输出,中断,复用功能的使用,该驱动源码在SDK的路径为:
以下就以该驱动为例介绍GPIO的操作。
本例子所需添加的DTS资源如下所示:
这里使用的是gslx680外设的reset(复位)引脚来讲述GPIO的输入输出操作。 在DTS配置如下资源:
AIO-PX30JD4的dts对引脚的描述与Firefly-RK3288有所区别,GPIO0_B4被描述为:<&gpio0 12 GPIO_ACTIVE_HIGH>,这里的12来源于:8+4=12,其中8是因为GPIO0_B4是属于GPIO0的B组,如果是A组的话则为0,如果是C组则为16,如果是D组则为24,以此递推,而4是因为B4后面的4。GPIO_ACTIVE_HIGH表示高电平有效,如果想要低电平有效,可以改为:GPIO_ACTIVE_LOW,这个属性将被驱动所读取。
这里使用的是gslx680外设的irq(中断) 引脚来讲述GPIO的中断功能 在DTS中配置如下资源:
其中 gpio0 5 的意思是使用gpio0_A5 为中断引脚,IRQ_TYPE_LEVEL_LOW意思是该引脚低电平(下降沿)的时候触发中断,跳到中断函数执行,中断的触发类型还可以配置如下:
查看芯片的数据手册,可以知道:
在上面i2c1的dts的配置中,主要有以下关键的描述
pinctrl-names 定义了状态名称列表: default (i2c 功能) 和 gpio 两种状态。
pinctrl-0 定义了状态 0 (即 default)时需要设置的 pinctrl: &i2c4_xfer
pinctrl-1 定义了状态 1 (即 gpio)时需要设置的 pinctrl: &i2c4_gpio
由于在i2c1的dts上gpio的字段属性没有添加,所以默认该两个引脚设置为i2c复用功能,其中pinctrl的描述可以在kernel/arch/arm64/boot/dts/rockchip/px30.dtsi 找到 :
其中 0 RK_PC2 表示的是GPIO0_C2引脚,0 RK_PC3 表示的是 GPIO0_C3引脚
RK_FUNC_1,RK_FUNC_GPIO的定义在 kernel/include/dt-bindings/pinctrl/rockchip.h 中可以找到:
在复用时,如果选择了 “default” (即 i2c 功能),系统会应用 i2c1_xfer 这个 pinctrl,最终将 GPIO0_C2 和 GPIO0_C3 两个针脚切换成对应的 i2c 功能;而如果选择了 “gpio” ,系统会应用 i2c1_gpio 这个 pinctrl,将 GPIO0_C2 和 GPIO0_C3 两个针脚还原为 GPIO 功能。
由于px30的i2c都是默认复用的,所以在源SDK的px30.dtsi中并没有加上gpio的选择,所以,在i2c总线驱动中:kernel/drivers/i2c/busses/i2c-rk3x.c,并没有加上切换复用功能的源码
如需了解i2c总线驱动是如何切换复用功能的,可以参考源码SDK中的rockchip的官方例子:kernel/drivers/i2c/busses/i2c-rockchip.c 中的rockchip_i2c_probe()函数。
首先是调用 of_get_gpio 取出设备树中 i2c1 结点的 gpios 属于所定义的两个 gpio:
然后是调用 devm_gpio_request 来申请 gpio,接着是调用 pinctrl_lookup_state 来查找 “gpio” 状态,而默认状态 “default” 已经由框架保存到 i2c->dev-pins->default_state 中了。最后调用 pinctrl_select_state 来选择是 “default” 还是 “gpio” 功能。
下面是常用的GPIO复用 API的定义:
以下是对px30源SDK中gslx680外设驱动中gsl_ts_probe()函数,gslX680_init()函数,static irqreturn_t gsl_ts_irq()进行部分的解析,用户可以从中了解gpio的输入输出,中断功能的使用,而用于复用功能的i2c通信,则在下一章i2c进行讲解。
of_get_named_gpio_flags 从设备树中读取 “reset-gpio” 和 “touch-gpio” 的 GPIO 配置编号和标志,gpio_is_valid 判断该 GPIO 编号是否有效,devm_gpio_request_one则申请占用该 GPIO。如果初始化过程出错,会跳到dev_err()函数进行报错与gpio资源释放处理。
调用gpio_to_irq把GPIO的PIN值转换为相应的IRQ值,调用devm_request_threaded_irq申请中断,如果失败会goto到标签error_req_irq_fail进行错误处理,gpio资源的释放,该函数中ts->irq是要申请的硬件中断号,gsl_ts_irq是中断函数,irq_flags | IRQF_ONESHOT是中断标志位, client->name是设备驱动程序名称,ts是该设备的device结构体,在注册共享中断时会用到。
在gsl_ts_probe()中,会在上电的时候,对复位引脚以及中断引脚进行初始化操作:
由上面的步骤可知晓,在设备上电的时候,可以用示波器测试出,该reset引脚会出现一个复位的操作。
而对于中断而言,在用户在进入系统之后,点击触摸屏,会把中断引脚拉低,由上面的devm_request_threaded_irq()函数可知,该中断在触发的时候,会跳到static irqreturn_t gsl_ts_irq()中断函数中去执行:
上面这个中断函数主要是做了一些键值的上报,由于本文未涉及input输入子系统,所以不在此处讲述。
下面是常用的 GPIO 输入输出的API 定义:
GPIO调试有一个很好用的工具,那就是IO指令,Android系统默认已经内置了IO指令,使用IO指令可以实时读取或写入每个IO口的状态,这里简单介绍IO指令的使用。 首先查看 io 指令的帮助:
从帮助上可以看出,如果要读或者写一个寄存器,可以用:
使用示例:
查看GPIO0当前各引脚值的情况
从主控的datasheet查到GPIO0_IOMUX对应寄存器基地址为:FF040000
如果想改变GPIO的配置值,可以使用以下指令设置:
Debugfs文件系统目的是为开发人员提供更多内核数据,方便调试。 这里GPIO的调试也可以用Debugfs文件系统,获得更多的内核信息。 GPIO在Debugfs文件系统中的接口为 /sys/kernel/debug/gpio,可以这样读取该接口的信息:
从读取到的信息中可以知道,内核把GPIO当前的状态都列出来了,以GPIO0组为例,gpio-5(GPIO0_A5)作为gslX680 模块的中断引脚,设置输入,输出高电平。
A1: 当使用GPIO request时候,会将该PIN的MUX值强制切换为GPIO,所以使用该pin脚为GPIO功能的时候确保该pin脚没有被其他模块所使用。
A2: 如果用IO命令读某个GPIO的寄存器,读出来的值异常,如 0x00000000或0xffffffff等,请确认该GPIO的CLK是不是被关了,GPIO的CLK是由CRU控制,可以通过读取datasheet下面CRU_CLKGATE_CON* 寄存器来查到CLK是否开启,如果没有开启可以用io命令设置对应的寄存器,从而打开对应的CLK,打开CLK之后应该就可以读到正确的寄存器值了。
A3: 测量该PIN脚的电压不对时,如果排除了外部因素,可以确认下该pin所在的io电压源是否正确,以及IO-Domain配置是否正确。
A4: 如果使用该GPIO时,不会动态的切换输入输出,建议在开始时就设置好GPIO 输出方向,后面拉高拉低时使用gpio_set_value()接口,而不建议使用gpio_direction_output(), 因为gpio_direction_output接口里面有mutex锁,对中断上下文调用会有错误异常,且相比 gpio_set_value,gpio_direction_output 所做事情更多,浪费。
AIO-PX30-JD4 开发板上有 4 个片上 I2C 控制器,各个 I2C 的使用情况如下表:
本文主要描述如何在该开发板上配置 I2C。
配置 I2C 可分为两大步骤:
定义和注册 I2C 设备
定义和注册 I2C 驱动
下面以配置 GSL3680 (触摸屏)为例。
在注册I2C设备时,需要结构体 i2c_client 来描述 I2C 设备。然而在标准Linux中,用户只需要提供相应的 I2C 设备信息,Linux就会根据所提供的信息构造 i2c_client 结构体。
用户所提供的 I2C 设备信息以节点的形式写到 dts 文件中,路径为 kernel/arch/arm64/boot/dts/rockchip/px30-firefly-aiojd4-lvds.dts ,如下所示:
该驱动的路径为:kernel/drivers/input/touchscreen/gslx680_firefly.c
在定义 I2C 驱动之前,用户首先要定义变量 of_device_id 和 i2c_device_id 。
of_device_id 用于在驱动中调用dts文件中定义的设备信息,其定义如下所示:
定义变量 i2c_device_id:
i2c_driver 如下所示:
注:变量id_table指示该驱动所支持的设备。
使用i2c_add_driver函数注册 I2C 驱动。
在调用 i2c_add_driver 注册 I2C 驱动时,会遍历 I2C 设备,如果该驱动支持所遍历到的设备(即id_table的值与设备树的compatible属性值相同),则会调用该驱动的 probe 函数。
在注册好 I2C 驱动后,即可进行 I2C 通讯。
在该驱动的gsl_ts_probe()函数中,会对gslx680的IC进行初始化,而在初始化的代码中,会对主从设备的通讯进行一个测试
而在 test_i2c()这个函数中,会存在gsl_ts_read(),gsl_ts_write()两个gslx680驱动自己封装的主机发送和主机接受函数,其内部真正调用的是Linux内核提供的I2C通讯函数。
向从机发送信息:
向从机读取信息:
在使用i2c_master_xxx()函数来进行接受或者发送的时候,也是调用i2c_transfer()这个函数来处理一个消息结构体(i2c_msg),而对于一些处理信息比较复杂的I2C设备,可以直接调用i2c_transfer()来处理信息,不过要自己构造 i2c_msg 结构体。
A1: 请检查硬件上拉是否给电。
A2: 返回值为-6表示为NACK错误,即对方设备无应答响应,这种情况一般为外设的问题,常见的有以下几种情况:
I2C地址错误,解决方法是测量I2C波形,确认是否I2C 设备地址错误;
I2C slave 设备不处于正常工作状态,比如未给电,错误的上电时序等;
时序不符合 I2C slave设备所要求也会产生Nack信号。
A3: 这时需要调用两次i2c_transfer, I2C read 拆分成两次,修改如下:
AIO-PX30-JD4 开发板上使用红外收发传感器 IR (麦克风和i2c0之间)实现遥控功能,在IR接口处接上红外接收器。本文主要描述在开发板上如何配置红外遥控器。
其配置步骤可分为两个部分:
修改内核驱动:内核空间修改,Linux 和 Android 都要修改这部分的内容。
修改键值映射:用户空间修改(仅限 Android 系统)。
在PX30的DTS文件 : kernel/arch/arm64/boot/dts/rockchip/px30-firefly-aiojd4-lvds.dts 中:
注1:第一列为键值,第二列为要响应的按键码。 注2:由于UART3的RX与IR复用了,所以要使用IR功能,就需要在设备树上关闭UART3。
在 Linux 内核中,IR 驱动仅支持 NEC 编码格式。以下是在内核中配置红外遥控的方法。
所涉及到的文件
在 remotectl_do_something 函数中获取用户码和键值:
注:用户可以使用 DBG_CODE() 函数打印用户码。
使用下面命令可以使能DBG_CODE打印:
将 IR 驱动编译进内核的步骤如下所示:
(1)、向配置文件 drivers/input/remotectl/Kconfig 中添加如下配置:
(2)、修改 drivers/input/remotectl 路径下的 Makefile,添加如下编译选项:
(3)、在 kernel 路径下使用 make menuconfig ,按照如下方法将IR驱动选中。
保存后,执行 make 命令即可将该驱动编进内核。
文件 /system/usr/keylayout/ff200030_pwm.kl 用于将 Linux 层获取的键值映射到 Android 上对应的键值。用户可以添加或者修改该文件的内容以实现不同的键值映射。
该文件内容如下所示:
注:通过 adb 修改该文件重启后即可生效。
下图是当红外遥控器按钮按下的时候,所产生的波形,主要由head,Control,information,signed free这四部分组成,具体可以参考RC6 Protocol。
AIO-PX30-JD4开发板默认外置支持了一个LCD屏接口,为LVDS,另外板子也支持MIPI屏幕,但需要注意的是MIPI和LVDS是复用的,使用LVDS之后不能使用MIPI,接口如下图:
由于AIO-PX30-JD4默认使用的是LVDS屏幕,同时在默认的配置文件kernel/arch/arm64/configs/firefly_defconfig已经把LCD相关的配置设置好了,如果自己做了修改,请注意把以下配置加上:
AIO-PX30-JD4中关于LVDS(MIPI) DSI_PHY的DTS配置在:kernel/arch/arm64/boot/dts/rockchip/px30.dtsi中,从该文件我们可以看到:
而在kernel/arch/arm64/boot/dts/rockchip/px30-firefly-aiojd4-lvds.dts 也存在对以上dts进行引用配置
AIO-PX30-JD4开发板外置了一个背光接口用来控制屏幕背光,如下图所示:
主要有背光电源引脚以及控制亮度引脚,DTS:kernel/arch/arm64/boot/dts/rockchip/px30-firefly-aiojd4-lvds.dts配置如下
enable-gpios 属性为背光的电源控制引脚。
pwms属性:配置PWM,可用来改变输出占空比(范例里面默认使用pwm0,50000ns是周期(20 KHz)。
brightness-levels属性:配置背光亮度数组,最大值为255,配置暗区和亮区,并把亮区数组做255的比例调节。比如范例中暗区是255-221,亮区是220-0。 由于PX30使用200以上的level屏幕就会过暗,所以默认最大值为200。
default-brightness-level属性:开机时默认背光亮度,范围为0-255。
具体请参考kernel中的说明文档:kernel/Documentation/devicetree/bindings/leds/backlight/pwm-backlight.txt
在kernel/arch/arm64/boot/dts/rockchip/px30-firefly-aiojd4-lvds.dts中可以看到以下语句:
时序属性参考下图:
lvds屏上完电后需要完成一些初始化的工作才可以工作。
kernel 部分 -> kernel/drivers/gpu/drm/panel/panel-simple.c
u-boot 部分 ->u-boot/drivers/video/drm/rockchip-dw-mipi-dsi.c
详细流程说明可参考以下附件: Rockchip DRM Panel Porting Guide.pdf
AIO-PX30-JD4 开发板上有 2 个 LED 灯,如下表所示:
以设备的方式控制 LED可通过使用 LED 设备子系统或者直接操作 GPIO 控制该 LED。
标准的 Linux 专门为 LED 设备定义了 LED 子系统。 在 AIO-PX30-JD4 开发板中的两个 LED 均以设备的形式被定义。
用户可以通过 /sys/class/leds/ 目录控制这两个 LED。
开发板上的 LED 的默认状态为:
Blue: 系统上电时打开
Yellow:用户自定义
用户可以通过 echo 向其 brightness属性输入命令控制每一个 LED:
Trigger 包含多种方式可以控制LED,这里就用两个例子来说明
Simple trigger LED
Complex trigger LED
更详细的说明请参考 kernel/Documentation/leds/leds-class.txt ,有内核对LED相关功能的支持的描述。
首先我们需要知道定义多少个LED,同时对应的LED的属性是什么。
在 kernel/arch/arm64/boot/dts/rockchip/px30-firefly-aiojd4-lvds.dts 文件中定义LED节点,具体定义如下:
注意:compatible 的值要跟 drivers/leds/leds-gpio.c 中的 .compatible 的值要保持一致。
这是使用简单的触发方式控制来LED,如下就默认打开黄灯:
(1)定义 LED 触发器 在kernel/drivers/leds/trigger/led-firefly-demo.c 文件中有如下添加
(2)注册该触发器
(3)控制 LED 的亮。
(4)打开LED demo
led-firefly-demo默认没有打开,如果需要的话可以使用以下补丁打开demo驱动:
如下是trigger方式控制LED复杂一点的例子,timer trigger 就是让LED达到不断亮灭的效果
我们需要在内核把timer trigger配置上
在 kernel 路径下使用 make menuconfig ,按照如下方法将timer trigger驱动选中。
保存配置并编译内核,把kernel.img 烧到AIO-PX30-JD4板子上 我们可以使用串口输入命令,就可以看到蓝灯不停的间隔闪烁
用户还可以使用 cat 命令获取 trigger 的可用值:
AIO-PX30-JD4 开发板带有一个MIPI camera,为MIPI_CSI,MIPI最高支持 3264x2448 pixels拍照。
本文以 OV13850 摄像头为例,讲解在该开发板上的配置过程。
kernel/arch/arm64/boot/dts/rockchip/px30.dtsi:
与摄像头相关的代码目录如下:
设置摄像头相关的引脚和时钟,即可完成配置过程。
从以下摄像头接口原理图可知,需要配置的引脚有:CIF_PWR、DVP_PWR和MIPI_RST。
mipi接口
DVP_PWR 对应 PX30 的 GPIO1_B7;
CIF_PWR 对应 PX30 的 GPIO1_B6;
MIPI_RST对应 PX30 的 GPIO2_B3;
在开发板中,这三个引脚都是在 cam_board_rk3326.xml 中设置。
修改hardware/rockchip/camera/Config/cam_board_rk3326.xml 来注册摄像头:
全部0条评论
快来发表一下你的评论吧 !