不同芯片厂商配置keypad 的方式不一样,基本原理一样“将sar 口电压值分成几个区间,每个区间映射一个KeyCode,然后注册一个input 设备,将keycode 抛上去”,上层应用通过捕获keycode 做逻辑处理。
我们先看一下电路连接
pin E15 作为SARADC_CH0 接收按键板产生的电压变化,根据配置区间映射上抛keycode。
adc_keypad 示例
Required properties:
- compatible: Must be "amlogic, adc_keypad".
- key_name: Specify the name of different key.
- key_num: Specify the quantity of the keys.
- key_code: Set the function code corresponding to different key.
- key_chan: Set the channel to detect the key, can be set from 0 to 7.
- key_val: The adc sampling value of different key, can be set from 0 to 1023.
- key_tolerance: tolerance of the adc sampling value, recommend maximum value of 40.
Optional properties:
- status: Shall be "ok" or "okay" if enabled or "disabled" if disabled.
Default is "ok".
Example:
adc_keypad{
compatible = "amlogic, adc_keypad";
status = "okay";
key_name = "left", "right","up", "down", "enter";
key_num = <5>;
key_code = <106 105 103 108 28>;
key_chan = <2 2 3 3 3>;
key_val = <516 253 516 750 0>; //voltage=0/252/478/692/824mV, val=voltage/1800mV*1023
key_tolerance = <40 40 40 40 40>;
};
定义了key_name,key_code,key_val,key_num分别表示按键名称,按键的码值,按键定义的电压值,按键的数量
定义adc_keypad
arch\arm64\boot\dts\amlogic\axg_s420.dts
adc_keypad {
compatible = "amlogic, adc_keypad";
status = "okay";
key_name = "power", "vol-", "vol+", "wifi", "<<", ">>"; // 按键名称
key_num = <6>; // 按键个数
io-channels = <&saradc SARADC_CH0>;
io-channel-names = "key-chan-0";
key_chan = <SARADC_CH0 SARADC_CH0 SARADC_CH0
SARADC_CH0 SARADC_CH0 SARADC_CH0>; // 表示每个按键对应的SAR通道
key_code = <116 114 115 139 105 106>; // 键值
key_val = <0 143 266 389 512 635>; //val=voltage/1800mV*1023 电压
key_tolerance = <40 40 40 40 40 40>;
};
SARADC_CH0 值SAR 通道,在amlogic-saradc.h中
#ifndef _DT_BINDINGS_IIO_ADC_AMLOGIC_H
#define _DT_BINDINGS_IIO_ADC_AMLOGIC_H
#define SARADC_CH0 0
#define SARADC_CH1 1
#define SARADC_CH2 2
#define SARADC_CH3 3
#define SARADC_CH4 4
#define SARADC_CH5 5
#define SARADC_CH6 6
#define SARADC_CH7 7
#define SARADC_CH_NUM 8
#endif
io-channels 引用的saradc 在mesonaxg.dtsi 中定义
saradc:saradc {
compatible = "amlogic,meson-axg-saradc";
status = "okay";
#io-channel-cells = <1>;
clocks = <&xtal>, <&clkc CLKID_SARADC_GATE>;
clock-names = "xtal", "saradc_clk";
interrupts = <GIC_SPI 73 IRQ_TYPE_EDGE_RISING>;
reg = <0x0 0xff809000 0x0 0x38>;
};
驱动解析aml_keypad 属性
meson_adc_kp_get_devtree_pdata @drivers\amlogic\input\keyboard\adc_keypad.c
完成key_name,key_code,key_chan,key_val,key_tolerance等设备属性解析
static int meson_adc_kp_get_devtree_pdata(struct platform_device *pdev,
struct meson_adc_kp *kp)
{
int ret;
int count;
int value;
int state = 0;
unsigned char cnt;
const char *uname;
unsigned int key_num;
struct adc_key *key;
struct of_phandle_args chanspec;
if (!pdev->dev.of_node) {
dev_err(&pdev->dev, "failed to get device node\n");
return -EINVAL;
}
count = of_property_count_strings(pdev->dev.of_node,
"io-channel-names");
if (count < 0) {
dev_err(&pdev->dev, "failed to get io-channel-names");
return -ENODATA;
}
ret = of_property_read_u32(pdev->dev.of_node, "poll-interval", &value);
if (ret)
kp->poll_period = POLL_INTERVAL_DEFAULT;
else
kp->poll_period = value;
for (cnt = 0; cnt < count; cnt++) {
ret = of_parse_phandle_with_args(pdev->dev.of_node,
"io-channels", "#io-channel-cells", cnt, &chanspec);
if (ret)
return ret;
if (!chanspec.args_count)
return -EINVAL;
if (chanspec.args[0] >= SARADC_CH_NUM) {
dev_err(&pdev->dev, "invalid channel index[%u]\n",
chanspec.args[0]);
return -EINVAL;
}
ret = of_property_read_string_index(pdev->dev.of_node,
"io-channel-names", cnt, &uname);
if (ret < 0) {
dev_err(&pdev->dev, "invalid channel name index[%d]\n",
cnt);
return -EINVAL;
}
kp->pchan[chanspec.args[0]] = devm_iio_channel_get(&pdev->dev,
uname);
if (IS_ERR(kp->pchan[chanspec.args[0]]))
return PTR_ERR(kp->pchan[chanspec.args[0]]);
}
ret = of_property_read_u32(pdev->dev.of_node, "key_num", &key_num);
if (ret) {
dev_err(&pdev->dev, "failed to get key_num!\n");
return -EINVAL;
}
for (cnt = 0; cnt < key_num; cnt++) {
key = kzalloc(sizeof(struct adc_key), GFP_KERNEL);
if (!key) {
dev_err(&pdev->dev, "alloc mem failed!\n");
return -ENOMEM;
}
ret = of_property_read_string_index(pdev->dev.of_node,
"key_name", cnt, &uname);
if (ret < 0) {
dev_err(&pdev->dev, "invalid key name index[%d]\n",
cnt);
state = -EINVAL;
goto err;
}
strncpy(key->name, uname, MAX_NAME_LEN);
ret = of_property_read_u32_index(pdev->dev.of_node,
"key_code", cnt, &key->code);
if (ret < 0) {
dev_err(&pdev->dev, "invalid key code index[%d]\n",
cnt);
state = -EINVAL;
goto err;
}
ret = of_property_read_u32_index(pdev->dev.of_node,
"key_chan", cnt, &key->chan);
if (ret < 0) {
dev_err(&pdev->dev, "invalid key chan index[%d]\n",
cnt);
state = -EINVAL;
goto err;
}
if (!kp->pchan[key->chan]) {
dev_err(&pdev->dev, "invalid channel[%u], please enable it first by DTS\n",
key->chan);
state = -EINVAL;
goto err;
}
ret = of_property_read_u32_index(pdev->dev.of_node,
"key_val", cnt, &key->value);
if (ret < 0) {
dev_err(&pdev->dev, "invalid key value index[%d]\n",
cnt);
state = -EINVAL;
goto err;
}
ret = of_property_read_u32_index(pdev->dev.of_node,
"key_tolerance", cnt, &key->tolerance);
if (ret < 0) {
dev_err(&pdev->dev, "invalid key tolerance index[%d]\n",
cnt);
state = -EINVAL;
goto err;
}
list_add_tail(&key->list, &kp->adckey_head);
}
meson_adc_kp_get_valid_chan(kp);
return 0;
err:
kfree(key);
return state;
}
input poll device 注册
static int meson_adc_kp_probe(struct platform_device *pdev)
{
......
// send data to bl301 for machine resume
send_data_to_bl301();
// lock/unlock keypad
kernel_keypad_enable_mode_enable();
......
// parse the device tree
ret = meson_adc_kp_get_devtree_pdata(pdev, kp);
if (ret)
goto err;
/*alloc input poll device*/
kp->poll_dev = devm_input_allocate_polled_device(&pdev->dev);
if (!kp->poll_dev) {
dev_err(&pdev->dev, "alloc input poll device failed!\n");
ret = -ENOMEM;
goto err;
}
......
meson_adc_kp_init_keybit(kp);
input->name = "adc_keypad";
input->phys = "adc_keypad/input0";
input->dev.parent = &pdev->dev;
input->id.bustype = BUS_ISA;
input->id.vendor = 0x0001;
input->id.product = 0x0001; // set vendor id
input->id.version = 0x0100; // set product id
input->rep[REP_DELAY] = 0xffffffff;
input->rep[REP_PERIOD] = 0xffffffff;
input->keycodesize = sizeof(unsigned short);
input->keycodemax = 0x1ff;
......
/*init class*/
......
/*register input poll device*/
ret = input_register_polled_device(kp->poll_dev);
if (ret) {
dev_err(&pdev->dev,
"unable to register keypad input poll device.\n");
goto err1;
}
return ret;
err1:
class_unregister(&kp->kp_class);
err:
meson_adc_kp_list_free(kp);
kfree(kp);
return ret;
}
完成input poll 设备的注册之后,就会生成设备节点。
aml_keypad 属性查看
通过命令cat /sys/class/adc_keypad/table,会调用table_show函数
static ssize_t table_show(struct class *cls, struct class_attribute *attr,
char *buf)
{
struct meson_adc_kp *kp = container_of(cls,
struct meson_adc_kp, kp_class);
struct adc_key *key;
unsigned char key_num = 1;
int len = 0;
mutex_lock(&kp->kp_lock);
list_for_each_entry(key, &kp->adckey_head, list) {
len += sprintf(buf+len,
"[%d]: name=%-21s code=%-5d channel=%-3d value=%-5d tolerance=%-5d\n",
key_num,
key->name,
key->code,
key->chan,
key->value,
key->tolerance);
key_num++;
}
mutex_unlock(&kp->kp_lock);
return len;
}
执行结果如下:
adc_keypad 应用
下图展现了整个aml_keypad 的整个按键的流程
SARADC 其它应用
通过SAR口映射HW Ver ,以便兼容不同HW。也可以保护自家产品。