Android touch详解(2)porting drvier
前言
本篇文章是根据本人实际项目的touch开发流程编写的,如有遗漏欢迎补充
了解需求
在一个项目的起始阶段,首先要了解该项目要用的touch 模组或touch IC;阅读touch模组和touch Ic spec;整理touch相关的需求,包括时序要求、电压要求、I2C从机地址、wakeup的实现等。确认有无特殊的功能要求。
电路图
review硬件电路图,确认touch使用的gpio只有touch使用、检查soc的spec确认中断gpio是否具有wakeup能力;I2C是否有上拉,有没有对应测试点。
确认touch需要enable的电源,一般包括,I2C上拉电源、touch IC电源、level shift电源;确保touch相关电源都是畅通的;
驱动移植
1. 添加touch driver
将驱动代码移植到目录" \drivers\input\touchscreen\sis_i2c_95xx " 下,“sis_i2c_95xx” 是touch驱动代码所在的文件夹,一般命名方式为:厂商缩写_(i2c/usb_)芯片型号
2. 修改Makefile
\drivers\input\touchscreen\sis_i2c_95xx\Makefile
#
# # Makefile for the sis95xx touchscreen drivers.
#
obj-$(CONFIG_TOUCHSCREEN_SIS_I2C_95XX) += sis_i2c.o
修改上级目录Makefile
obj-$(CONFIG_TOUCHSCREEN_SIS_I2C_95XX) += sis_i2c_95xx/
obj-$(CONFIG_XXX) += sis_i2c.o,表示在这个目录中有一个名为sis_i2c.o的目标文件。sis_i2c.o将从sis_i2c.c 或sis_i2c.S文件编译得到
其中 $(CONFIG_XXX)代表引用了CONFIG_XXX变量,CONFIG_XXX一般定义在.ini文件中,可以为y(编译进内核)或m(编译成模块),如果CONFIG_XXX的不是y或m就不会编译连接
3. kconfig
\drivers\input\touchscreen\sis_i2c_95xx\Kconfig
#
# STMicroelectronics touchscreen driver configuration
#
config TOUCHSCREEN_SIS_I2C_95XX
tristate "SiS95xx series I2C touchscreen driver"
depends on I2C
help
Say Y here to enable support for I2C connected ilitek touch panels.
If unsure, say N.
To compile this driver as a module, choose M here: the
module will be called sis_i2c.
修改上级目录Kconfig
source "drivers/input/touchscreen/sis_i2c_95xx/Kconfig"
config是关键字,表示一个配置选项的开始;紧跟着的TOUCHSCREEN_SIS_I2C_95XX是配置选项的名称。编译过程中会以arch\arm64\Kconfig为起点逐层解析Kconfig,在TOUCHSCREEN_SIS_I2C_95XX前面加入前缀"CONFIG_"
tristate表示变量类型,即"CONFIG_TOUCHSCREEN_SIS_I2C_95XX"的类型,有5种类型:bool、tristate、string、hex和int,其中 tristate和string是基本的类型
depends on:表示依赖于XXX,“depends on I2C”表示只有当I2C配置选项被选中时,当前配置选项的提示信息才会出现,才能设置当前配置选项
4. .ini文件
\mpos_config\hw_config\hw_common.ini
CONFIG_TOUCHSCREEN_SIS_I2C_95XX=y
设置CONFIG_TOUCHSCREEN_SIS_I2C_95XX的值,y为编译进内核、m为编译成模块。在编译过程中会通过Makefile、Kconfig和.ini中的定义一起结合生成.config文件。总结来说,Kconfig中去定义"CONFIG_XXX"变量,Makefile中引用变量看是否编译,.ini定义变量的值。
\android_out\ …\target\product\ …\obj\KERNEL_OBJ\ .config
CONFIG_TOUCHSCREEN_SIS_I2C_95XX=y
若没有定义变量的值,则"CONFIG_XXX"的值为n(不编译)
5. DTS
修改对应dts文件,通过I2C SCL和SDA的gpio确定touch挂在哪个I2C下面,设置compatible匹配的driver、addr,设置irq、reset、电源的gpio,设置pinctrl的名字和状态。
\arch\arm64\boot\dts\qcom\
&tlmm {
sis_ts_default {
sis_ts_reset_default: sis_ts_reset_default {
mux {
pins = "gpio66";
function = "gpio";
};
config {
pins = "gpio66";
drive-strength = <2>;
bias_disable;
};
};
sis_ts_int_default: sis_ts_int_default {
mux {
pins = "gpio107";
function = "gpio";
};
config {
pins = "gpio107";
drive-strength = <2>;
bias_pull_down;
};
};
sis_ts_through_default: sis_ts_through_default {
mux {
pins = "gpio113";
function = "gpio";
};
config {
pins = "gpio113";
drive-strength = <2>;
bias_disable;
};
};
};
};
&i2c_4 {
status = "okay";
/* sis touch configuration */
sis_touchscreen@5c {
compatible = "sis,sis_touch"; //和驱动中保持一致
reg = <0x5c>;// I2C地址
interrupt-parent = <&tlmm>;
interrupts = <107 0x0>;
sis,irq-gpio = <&tlmm 107 0x00>;
sis,reset-gpio = <&tlmm 66 0x00>;
sis,through-gpio = <&tlmm 113 0x00>;
// 电源
p5v_usb-supply = <&p5v_usb>;
vregl13-supply = <&pm660_l13>;
vregl11-supply = <&pm660_l11>;
p3v3-supply = <&p3v3>;
pinctrl-names = "sis_ts_default";
pinctrl-0 = <&sis_ts_reset_default &sis_ts_int_default &sis_ts_through_default>;
status = "disabled";
};
};
注意:compatible要与kernel driver中的of_match_table中的compatible一致
static struct of_device_id sis_ts_dt_ids[] = {
{ .compatible = "sis,sis_touch" },
{ }
};
static struct i2c_driver sis_ts_driver = {
.probe = sis_ts_probe,
.remove = sis_ts_remove,
.id_table = sis_ts_id,
.shutdown = sis_ts_shutdown,
.driver = {
.name = SIS_I2C_NAME,
.owner = THIS_MODULE,
.of_match_table = of_match_ptr(sis_ts_dt_ids),
},
};
上面status = “disabled”;表示默认disabled,在对应项目的dts中enable。这样做一方面是可以将touch相关的dts都放在同一个dtsi中方便修改和管理;另一方面由于有时不同设备用的同一个touch驱动,虽然是同一个驱动但每个设备要求的配置可能不一样,那么我们会在dts中去区分,比如不同设备有不同的dts文件。
&i2c_4 {
sis_touchscreen@5c {
//定义分辨率
sis,max-x = <1919>;
sis,max-y = <1079>;
status = "okay";
};
};
6. 修改相关权限
\system\core\rootdir\ueventd.rc
\system\core\rootdir\init.rc
根据实际需求定义权限
#sis
/dev/sis_hydra_touch_device 0666 root root
准备code
1. 申请电源
申请touch用到的所有电源。找到每个regulator对应的dts name,为保证一致性,在dts中去定义电源,.c去读取dts的字串,读取后再进行电源的enable和disable;touch电源如果有专用的控制IO,则根据电路图进行配置电平。
enum sis_regulator {
/*sync order to sis_regu[]*/
P3V3,
USB5V,
VREGL13,
VREGL11,
};
static struct sis_regulator_data {
const int index;
const char *dts_name;
const int voltage;
struct regulator *regu_name;
} sis_regu[] = {
{P3V3, "p3v3", 3300000, NULL},
{USB5V, "p5v_usb", 5000000, NULL},
{VREGL13, "vregl13", 1800000, NULL},
{VREGL11, "vregl11", 1800000, NULL},
{-1, NULL, NULL, NULL},
};
static int sis_regulator_power_on(struct sis_ts_data *ts, bool flag)
{
int i, ret = 0;
if(true == flag) {
for(i = 0; sis_regu[i].regu_name; i++) {
ret = regulator_enable(sis_regu[i].regu_name);
if (ret) {
dev_err(&ts->client->dev,
"failed to enable regulator: %d\n", ret);
return ret;
}
}
}
else {
for(i = 0; sis_regu[i].regu_name; i++) {
ret = regulator_disable(sis_regu[i].regu_name);
if (ret) {
dev_err(&ts->client->dev,
"failed to disable regulator: %d\n", ret);
return ret;
}
}
}
return ret;
}
static int sis_regulator_power_init(struct sis_ts_data *ts)
{
int i, ret = 0;
for(i = 0; sis_regu[i].dts_name; i++) {
sis_regu[i].regu_name = regulator_get(&ts->client->dev, sis_regu[i].dts_name);
ret = ERR_ALLOC_MEM(sis_regu[i].regu_name);
if (ret) {
dev_err(&ts->client->dev, "regulator_get %s fail\n", sis_regu[i].dts_name);
sis_regu[i].regu_name = NULL;
return ret;
}
ret = regulator_set_voltage(sis_regu[i].regu_name, sis_regu[i].voltage, sis_regu[i].voltage);
if (ret < 0) {
dev_err(&ts->client->dev, "Failed to set %d\n", sis_regu[i].voltage);
return ret;
}
}
ret = sis_regulator_power_on(ts, true);
if (ret) {
dev_err(&ts->client->dev,
"failed to sis_regulator_power_on: %d\n", ret);
return ret;
}
return ret;
}
static int sis_regulator_power_exit(struct sis_ts_data *ts)
{
int i;
sis_regulator_power_on(ts, false);
for(i = 0; sis_regu[i].dts_name; i++) {
if (!IS_ERR_OR_NULL(sis_regu[i].regu_name)) {
if (regulator_count_voltages(sis_regu[i].regu_name) > 0)
regulator_set_voltage(sis_regu[i].regu_name, 0, sis_regu[i].voltage);
regulator_put(sis_regu[i].regu_name);
}
}
return 0;
}
2. 修改分辨率
static int sis_ts_probe(
struct i2c_client *client, const struct i2c_device_id *id)
{
...
// 从dts中读取分辨率
if (of_property_read_u32(client->dev.of_node, "sis,max-x", &ts->max_x))
{
pr_err("%s: sis touch max_x not specified\n", __func__);
ret = ENXIO;
goto err_fb_notif_failed;
}
if (of_property_read_u32(client->dev.of_node, "sis,max-y", &ts->max_y))
{
pr_err("%s: sis touch max_y not specified\n", __func__);
ret = ENXIO;
goto err_fb_notif_failed;
}
...
input_set_abs_params(ts->input_dev, ABS_MT_POSITION_X,
0, SIS_MAX_X, 0, 0);
input_set_abs_params(ts->input_dev, ABS_MT_POSITION_Y,
0, SIS_MAX_Y, 0, 0);
...
}
touch这边不修改分辨率也没问题的,输入系统会根据display那边的分辨率信息去进行坐标转换。
3. 确认I2C从机地址
再次确认dts和驱动中的I2C地址是否和spec中一致
4. 申请GPIO
申请irq、reset 等GPIO。包括从dts读gpio名字、合法性检查、gpio request、设置输入输出、gpio free;中断gpio申请为IRQ,添加中断线程;
static int sis_ts_gpio_register(struct sis_ts_data *ts)
{
ts->irq_gpio = of_get_named_gpio(ts->client->dev.of_node, "sis,irq-gpio",0);
if (gpio_is_valid(ts->irq_gpio)) {// 合法性检查
gpio_request(ts->irq_gpio, // 申请gpio
"sis_touch_gpio_irq");
...
gpio_direction_input(ts->irq_gpio);// 设置方向为输入
}
...
return ret;
}
5. 睡眠和唤醒
添加callback和睡眠唤醒,如果需要wakeup睡眠时不要disable中断,不要关闭影响中断的的电源,保证从硬件产生中断到soc接受中断一路畅通;
static int sis_ts_suspend(struct i2c_client *client)
{
int ret = 0;
struct sis_ts_data *ts = i2c_get_clientdata(client);
if (socinfo_get_tp_wakeup()) {
regulator_disable(sis_regu[VREGL11].regu_name);
} else {
if (ts->use_irq) {
#if ( LINUX_VERSION_CODE < KERNEL_VERSION (4, 2, 0) )
if ((ts->desc->irq_data.state_use_accessors
& IRQD_IRQ_DISABLED) == IRQ_STATUS_ENABLED)
#else
if ((ts->desc->irq_common_data.state_use_accessors
& IRQD_IRQ_DISABLED) == IRQ_STATUS_ENABLED)
#endif
disable_irq(ts->client->irq);
} else
hrtimer_cancel(&ts->timer);
flush_workqueue(sis_wq); /* only flush sis_wq */
sis_regulator_power_on(ts, false);
return 0;
}
static int sis_ts_resume(struct i2c_client *client)
{
int ret = 0;
struct sis_ts_data *ts = i2c_get_clientdata(client);
if (socinfo_get_tp_wakeup()) {
regulator_enable(sis_regu[VREGL11].regu_name);
} else {
sis_regulator_power_on(ts, true);
if (ts->use_irq) {
#if ( LINUX_VERSION_CODE < KERNEL_VERSION (4, 2, 0) )
if ((ts->desc->irq_data.state_use_accessors
& IRQD_IRQ_DISABLED) == IRQ_STATUS_DISABLED)
#else
if ((ts->desc->irq_common_data.state_use_accessors
& IRQD_IRQ_DISABLED) == IRQ_STATUS_DISABLED)
#endif
enable_irq(ts->client->irq);
} else
hrtimer_start(&ts->timer, ktime_set(1, 0), HRTIMER_MODE_REL);
}
return 0;
}
static int fb_notifier_callback(struct notifier_block *self,
unsigned long event, void *data)
{
int blank;
struct fb_event *evdata = data;
struct sis_ts_data *ts_data = container_of(self, struct sis_ts_data, fb_notif);
if (evdata && evdata->data && ts_data && event == FB_EVENT_BLANK) {
blank = *(int *)(evdata->data);
if (blank == FB_BLANK_POWERDOWN)
sis_ts_suspend(ts_data->client);
else if (blank == FB_BLANK_UNBLANK)
sis_ts_resume(ts_data->client);
}
return 0;
}
6. 检查函数返回值
记得检查函数返回值和错误处理;示例代码为了简洁把检查返回值和错误处理都删掉了
7. debug level
添加debug level,用于打印一下调试信息
/* 1: Default, 0: No log. The bigger value, the more detailed log is output. */
#define CONFIG_SIS_DEBUG_LEVEL (0)
#define SIS_LOG_LEVEL_HIGH 2
#define DEBUG_LEVEL(level, fmt, arg...) do {\
if (level <= tp_debug_level)\
pr_info(fmt, ##arg);\
} while (0)
#define SIS_DBG(fmt, arg...) DEBUG_LEVEL(1, fmt, ##arg)
ADSP
1. gpio配置的xml
该文件中对gpio配置在BootLoader时期生效,进入kernel后如果有重新配置跟随新的配置,如果没有则一直有效。
\non_hlos\boot_images\QcomPkg\Sdm660Pkg\Settings\TLMM\loader\TLMMChipset_Handheld.xml
<var_seq name="DALTLMMBSP_LowPowerCfg" type=DALPROP_DATA_TYPE_UINT32_SEQ>
/* GPIO DIR, PULL, Outval Program*/
/* 0 */ DALTLMM_INPUT | DALTLMM_PULL_DOWN | DALTLMM_OUT_LOW | DALTLMM_PRG_NO,
...
第一个参数代表gpio是输入还是输出;第二个参数代表拉高、拉低还是no pull;第三个参数当gpio设为输出时,输出高电平还是低电平;第四个参数代表对这个gpio的配置是否生效。
2. i2c gpio权限配置
\non_hlos\trustzone_images\core\buses\qup_accesscontrol\honeybadger\config\QUPAC_660_Access.xml
I2C gpio权限应该为AC_HLOS
<device id=BLSP_QUP4_DEV_ACCESS>
<props name="PERIPH ID" type=DALPROP_ATTR_TYPE_UINT32> BLSP_QUP4 </props>
<props name="GPIO range" type=DALPROP_ATTR_TYPE_BYTE_SEQ> 14, 15, end </props>
<props name="IS_GPIO_PROTECTED" type=DALPROP_ATTR_TYPE_UINT32> 1 </props>
<props name="RW_ACCESS_LIST" type=DALPROP_ATTR_TYPE_BYTE_SEQ> AC_HLOS, end </props>
<props name="IS_PERSISTENT" type=DALPROP_ATTR_TYPE_UINT32> 0 </props>
</device>