Android touch详解(2) porting drvier

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_)芯片型号
Android touch详解(2) porting drvier

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>
上一篇:3226 元起,苹果 iPhone SE Plus 曝光:6.1 英寸屏幕,搭载 A14 芯片,支持 Touch ID


下一篇:JY04文件管理2