I2C设备驱动分析-基于imx6下tsc2007电阻触摸屏驱动

I2C设备驱动分析–基于imx6下tsc2007电阻触摸屏设备驱动


1. 主要数据结构说明

1.of_device_id (include/linux/mod_devicetable.h)
/* 用来和设备树中的节点匹配 */
struct of_device_id {
	char	name[32];
	char	type[32];
	char	compatible[128];
	const void *data;
};
2. struct device_driver (include/linux/device.h)
struct device_driver {
	/* 驱动名称 */
	const char		*name;
    /* 支持的device所依附的bus */
	struct bus_type *bus;
    /* 一般都是THIS_MODULE */
	struct module *owner;
	const char *mod_name; /* used for built-in modules */
	bool suppress_bind_attrs; /* disables bind/unbind via sysfs */
    /* 支持的设备列表,用于匹配设备树节点 */
	const struct of_device_id	*of_match_table;
	const struct acpi_device_id	*acpi_match_table;
	int (*probe) (struct device *dev);
	int (*remove) (struct device *dev);
	void (*shutdown) (struct device *dev);
	int (*suspend) (struct device *dev, pm_message_t state);
	int (*resume) (struct device *dev);
	const struct attribute_group **groups;
	const struct dev_pm_ops *pm;
    /* 私有数据指针 */
	struct driver_private *p;
}
3. struct i2c_driver (include/linux/i2c.h)

描述了一套i2c设备驱动方法。

struct i2c_driver {
	unsigned int class;
    /* 依附attach_adapter函数指针 */
	int (*attach_adapter)(struct i2c_adapter *) __deprecated;
    /* device和driver匹配上时执行 */
	int (*probe)(struct i2c_client *, const struct i2c_device_id *);
	int (*remove)(struct i2c_client *);
	void (*shutdown)(struct i2c_client *);
	void (*alert)(struct i2c_client *, unsigned int data);
	int (*command)(struct i2c_client *client, unsigned int cmd, void *arg);
	struct device_driver driver;
    /* 该驱动支持的设备ID列表 */
	const struct i2c_device_id *id_table;
	int (*detect)(struct i2c_client *, struct i2c_board_info *);
	const unsigned short *address_list;
	struct list_head clients;
};
4. struct i2c_client() (include/linux/i2c.h)

描述了一个真实的i2c设备,包括名称、地址等。

struct i2c_client {
	unsigned short flags;		/* div., see below		*/
	unsigned short addr;		/* chip address - NOTE: 7bit	*/
					/* addresses are stored in the	*/
					/* _LOWER_ 7 bits		*/
	char name[I2C_NAME_SIZE];
	struct i2c_adapter *adapter;	/* the adapter we sit on	*/
	struct device dev;		/* the device structure		*/
	int irq;			/* irq issued by device		*/
	struct list_head detected;
#if IS_ENABLED(CONFIG_I2C_SLAVE)
	i2c_slave_cb_t slave_cb;	/* callback for slave mode	*/
#endif
}
5. struct tsc2007
struct tsc2007 {
	/* input_dev */
	struct input_dev	*input;
	char				phys[32];
    /* 设备 */
	struct i2c_client	*client;
	u16					model;
	u16					x_plate_ohms;
    /* 最大压力 */
	u16					max_rt;
	unsigned long		poll_period; /* in jiffies */
	int					fuzzx;
	int					fuzzy;
	int					fuzzz;
	unsigned			gpio;
    /* 中断号 */
	int					irq;
    /* 等待队列头 */
	wait_queue_head_t	wait;
    /* 驱动是否停止标记 */
	bool				stopped;
    /* 获取触屏按下状态函数指针 */
	int	(*get_pendown_state)(struct device *);
};

2. 主要函数说明

1.probe函数

probe函数在device和driver匹配上的时候执行,主要负责申请驱动需要的内存、中断等资源,初始化驱动相关结构体成员,最后申请并注册input_dev设备。

/* 设备树读取 */
static int tsc2007_probe_dt(struct i2c_client *client, struct tsc2007 *ts)
{
	struct device_node *np = client->dev.of_node;
	u32 val32;
	u64 val64;

	if (!np) 
		return -EINVAL;

	if (!of_property_read_u32(np, "ti,max-rt", &val32))
		ts->max_rt = val32;
	else
		ts->max_rt = MAX_12BIT;

	if (!of_property_read_u32(np, "ti,fuzzx", &val32))
		ts->fuzzx = val32;

	if (!of_property_read_u32(np, "ti,fuzzy", &val32))
		ts->fuzzy = val32;

	if (!of_property_read_u32(np, "ti,fuzzz", &val32))
		ts->fuzzz = val32;

	if (!of_property_read_u64(np, "ti,poll-period", &val64))
		ts->poll_period = msecs_to_jiffies(val64);
	else
		ts->poll_period = msecs_to_jiffies(1);

	if (!of_property_read_u32(np, "ti,x-plate-ohms", &val32))
		ts->x_plate_ohms = val32;
	else
		return -EINVAL;

	/* 获取GPIO */
	ts->gpio = of_get_gpio(np, 0);
	if (gpio_is_valid(ts->gpio))
		ts->get_pendown_state = tsc2007_get_pendown_state_gpio;

	return 0;
}

/* probe function */
static int tsc2007_probe(struct i2c_client *client, 
			const struct i2c_device_id *id)
{
	struct tsc2007 *ts;
	struct input_dev *input_dev;
	int err;

	/* 给ts分配内存,devm_kzalloc分配的内存不需要手动释放 */
	ts = devm_kzalloc(&client->dev, sizeof(struct tsc2007), GFP_KERNEL);
	if (!ts)
		return -ENOMEM;
	/* 使用设备树属性配置ts */
	err = tsc2007_probe_dt(client, ts);
	if (err)
		return err;

	/* 为input_dev分配内存 */
	input_dev = devm_input_allocate_device(&client->dev);
	if (!input_dev)
		return -ENOMEM;
	
	i2c_set_clientdata(client, ts);

	ts->client = client;
	ts->irq = client->irq;
	ts->input = input_dev;
	/* 初始化等待队列头 */
	init_waitqueue_head(&ts->wait);
	input_dev->name = "TSC2007 TouchScreen";
	input_dev->phys = ts->phys;
	input_dev->id.bustype = BUS_I2C;
	input_dev->open = tsc2007_open;
	input_dev->close = tsc2007_close;
	input_set_drvdata(input_dev, ts);

	// 支持的事件类型
	input_dev->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_ABS);
	// 支持的按键值
	input_dev->keybit[BIT_WORD(BTN_TOUCH)] = BIT_MASK(BTN_TOUCH);
	/* 绝对坐标 */
	/* 设置上报数据类型,范围 */
	input_set_abs_params(input_dev, ABS_X, 0, MAX_12BIT, ts->fuzzx, 0);
	input_set_abs_params(input_dev, ABS_Y, 0, MAX_12BIT, ts->fuzzy, 0);
	input_set_abs_params(input_dev, ABS_PRESSURE, 0, MAX_12BIT, ts->fuzzz, 0);

	/* 请求软中断 */
	err = devm_request_threaded_irq(&client->dev, ts->irq, tsc2007_hard_irq,
								tsc2007_soft_irq, IRQF_ONESHOT,
								client->dev.driver->name, ts);
	if (err)
		return err;
	tsc2007_stop(ts);

	/* 注册input设备 */
	err = input_register_device(input_dev);
	if (err)
		return err;
	
	return 0;
}
2. open、close和stop函数

open和close函数负责驱动的打开和关闭。

/* open函数 */
static int tsc2007_open(struct input_dev *input_dev)
{
	struct tsc2007 *ts = input_get_drvdata(input_dev);
	int err;

	/* 标记为启动状态 */
	ts->stopped = false;
	mb();

	/* 开启外部中断 */
	enable_irq(ts->irq);

	/* 使tsc2007掉电,并开启中断 */
	err = tsc2007_xfer(ts, PWRDOWN);
	if (err < 0) {
		tsc2007_stop(ts);
		return err;
	}
	return 0;
}

/* close函数 */
static void tsc2007_close(struct input_dev *input_dev)
{
	struct tsc2007 *ts = input_get_drvdata(input_dev);

	tsc2007_stop(ts);
}

/* stop函数 */
static void tsc2007_stop(struct tsc2007 *ts)
{
	/* 标记为停止状态 */
	ts->stopped = true;
	mb();
	wake_up(&ts->wait);
	/* 关闭外部中断 */
	disable_irq(ts->irq);
}

3. 软中断和硬中断

当触摸按下时会进入外部中断硬中断,在硬中断中激活软中断,而读取并上报ad值等操作在软中断中进行。

/* 软中断 */
static irqreturn_t tsc2007_soft_irq(int irq, void *handler)
{
	struct tsc2007 *ts = handler;
	struct input_dev *input = ts->input;
	struct ts_event tc;
	u32 rt;

	/* 未stop且按下 ,如果一直按下会在这里循环*/
	while (!ts->stopped && tsc2007_is_pen_down(ts)) {
		/* 读取tsc2007原始数据 */
		tsc2007_read_values(ts, &tc);
		/* 计算压力 */
		rt = tsc2007_calculate_pressure(ts, &tc);
		if (!rt && !ts->get_pendown_state)
			break;
		if (rt <= ts->max_rt) {
			input_report_key(input, BTN_TOUCH, 1);
			input_report_abs(input, ABS_X, tc.x);
			input_report_abs(input, ABS_Y, tc.y);
			input_report_abs(input, ABS_PRESSURE, rt);
			input_sync(input);
		}
		/* 等待一段时 */
		wait_event_timeout(ts->wait, ts->stopped, ts->poll_period);
	}
	input_report_key(input, BTN_TOUCH, 0);
	input_report_abs(input, ABS_PRESSURE, 0);
	input_sync(input);

	return IRQ_HANDLED;
}

/* 硬中断 */
static  irqreturn_t tsc2007_hard_irq(int irq, void *handle)
{
	struct tsc2007 *ts = handle;
	/* 按下 */
	if (tsc2007_is_pen_down(ts))
		return IRQ_WAKE_THREAD;

	return IRQ_HANDLED;
}
4. 计算压力
/* 计算压力 */
static u32 tsc2007_calculate_pressure(struct tsc2007 *tsc, struct ts_event *tc)
{
	u32 rt = 0;
	if (tc->x == MAX_12BIT)
		tc->x = 0;

	if (likely(tc->x && tc->z1)) {
		rt = tc->z2 - tc->z1;
		rt *= tc->x;
		rt *= tsc->x_plate_ohms;
		rt /= tc->z1;
		rt = (rt + 2047) >> 12;
	}
	return rt;
}
5. 通过I2C读取坐标
/* i2c传输数据 */
static inline int tsc2007_xfer(struct tsc2007 *tsc, u8 cmd)
{
	s32 data;
	u16 val;
	data = i2c_smbus_read_word_data(tsc->client, cmd);
	if (data < 0) {
		return data;
	}
	val = swab16(data) >> 4;
	return val;
}

/* 从tsc2007中读取数据坐标 */
static void tsc2007_read_values(struct tsc2007 *tsc, struct ts_event *tc)
{
	tc->y = tsc2007_xfer(tsc, READ_Y);
	tc->x = tsc2007_xfer(tsc, READ_X);
	tc->z1 = tsc2007_xfer(tsc, READ_Z1);
	tc->z2 = tsc2007_xfer(tsc, READ_Z2);
	tsc2007_xfer(tsc, PWRDOWN);
}
6. 检测触屏是否按下
/* 触摸按下状态获取 */
static int  tsc2007_get_pendown_state_gpio(struct device *dev)
{
	struct i2c_client *client = to_i2c_client(dev);
	struct tsc2007 *ts = i2c_get_clientdata(client);

	/* 按下返回1 */
	return !gpio_get_value(ts->gpio);
}

/* 获取触屏按下状态 */
static bool tsc2007_is_pen_down(struct tsc2007 *ts)
{
	/* 没有此函数,函数指针为空,返回true */
	if(!ts->get_pendown_state)
		return true;
	else
		/* 获取按下状态 */
		return ts->get_pendown_state(&ts->client->dev);
}

你可能感兴趣的:(Linux内核驱动)