Skip to content
体验新版
项目
组织
正在加载...
登录
切换导航
打开侧边栏
OpenHarmony
kernel_linux
提交
cf45b5a2
K
kernel_linux
项目概览
OpenHarmony
/
kernel_linux
上一次同步 4 年多
通知
15
Star
8
Fork
2
代码
文件
提交
分支
Tags
贡献者
分支图
Diff
Issue
0
列表
看板
标记
里程碑
合并请求
0
DevOps
流水线
流水线任务
计划
Wiki
0
Wiki
分析
仓库
DevOps
项目成员
Pages
K
kernel_linux
项目概览
项目概览
详情
发布
仓库
仓库
文件
提交
分支
标签
贡献者
分支图
比较
Issue
0
Issue
0
列表
看板
标记
里程碑
合并请求
0
合并请求
0
Pages
DevOps
DevOps
流水线
流水线任务
计划
分析
分析
仓库分析
DevOps
Wiki
0
Wiki
成员
成员
收起侧边栏
关闭侧边栏
动态
分支图
创建新Issue
流水线任务
提交
Issue看板
提交
cf45b5a2
编写于
7月 29, 2012
作者:
D
Dmitry Torokhov
浏览文件
操作
浏览文件
下载
差异文件
Merge branch 'next' into for-linus
Prepare second set of changes for 3.6 merge window.
上级
314820c9
c0394506
变更
8
隐藏空白更改
内联
并排
Showing
8 changed file
with
1033 addition
and
3 deletion
+1033
-3
Documentation/input/edt-ft5x06.txt
Documentation/input/edt-ft5x06.txt
+54
-0
drivers/input/mouse/synaptics.c
drivers/input/mouse/synaptics.c
+22
-0
drivers/input/tablet/wacom_wac.c
drivers/input/tablet/wacom_wac.c
+19
-2
drivers/input/tablet/wacom_wac.h
drivers/input/tablet/wacom_wac.h
+2
-1
drivers/input/touchscreen/Kconfig
drivers/input/touchscreen/Kconfig
+13
-0
drivers/input/touchscreen/Makefile
drivers/input/touchscreen/Makefile
+1
-0
drivers/input/touchscreen/edt-ft5x06.c
drivers/input/touchscreen/edt-ft5x06.c
+898
-0
include/linux/input/edt-ft5x06.h
include/linux/input/edt-ft5x06.h
+24
-0
未找到文件。
Documentation/input/edt-ft5x06.txt
0 → 100644
浏览文件 @
cf45b5a2
EDT ft5x06 based Polytouch devices
----------------------------------
The edt-ft5x06 driver is useful for the EDT "Polytouch" family of capacitive
touch screens. Note that it is *not* suitable for other devices based on the
focaltec ft5x06 devices, since they contain vendor-specific firmware. In
particular this driver is not suitable for the Nook tablet.
It has been tested with the following devices:
* EP0350M06
* EP0430M06
* EP0570M06
* EP0700M06
The driver allows configuration of the touch screen via a set of sysfs files:
/sys/class/input/eventX/device/device/threshold:
allows setting the "click"-threshold in the range from 20 to 80.
/sys/class/input/eventX/device/device/gain:
allows setting the sensitivity in the range from 0 to 31. Note that
lower values indicate higher sensitivity.
/sys/class/input/eventX/device/device/offset:
allows setting the edge compensation in the range from 0 to 31.
/sys/class/input/eventX/device/device/report_rate:
allows setting the report rate in the range from 3 to 14.
For debugging purposes the driver provides a few files in the debug
filesystem (if available in the kernel). In /sys/kernel/debug/edt_ft5x06
you'll find the following files:
num_x, num_y:
(readonly) contains the number of sensor fields in X- and
Y-direction.
mode:
allows switching the sensor between "factory mode" and "operation
mode" by writing "1" or "0" to it. In factory mode (1) it is
possible to get the raw data from the sensor. Note that in factory
mode regular events don't get delivered and the options described
above are unavailable.
raw_data:
contains num_x * num_y big endian 16 bit values describing the raw
values for each sensor field. Note that each read() call on this
files triggers a new readout. It is recommended to provide a buffer
big enough to contain num_x * num_y * 2 bytes.
Note that reading raw_data gives a I/O error when the device is not in factory
mode. The same happens when reading/writing to the parameter files when the
device is not in regular operation mode.
drivers/input/mouse/synaptics.c
浏览文件 @
cf45b5a2
...
...
@@ -40,11 +40,27 @@
* Note that newer firmware allows querying device for maximum useable
* coordinates.
*/
#define XMIN 0
#define XMAX 6143
#define YMIN 0
#define YMAX 6143
#define XMIN_NOMINAL 1472
#define XMAX_NOMINAL 5472
#define YMIN_NOMINAL 1408
#define YMAX_NOMINAL 4448
/* Size in bits of absolute position values reported by the hardware */
#define ABS_POS_BITS 13
/*
* Any position values from the hardware above the following limits are
* treated as "wrapped around negative" values that have been truncated to
* the 13-bit reporting range of the hardware. These are just reasonable
* guesses and can be adjusted if hardware is found that operates outside
* of these parameters.
*/
#define X_MAX_POSITIVE (((1 << ABS_POS_BITS) + XMAX) / 2)
#define Y_MAX_POSITIVE (((1 << ABS_POS_BITS) + YMAX) / 2)
/*****************************************************************************
* Stuff we need even when we do not want native Synaptics support
...
...
@@ -588,6 +604,12 @@ static int synaptics_parse_hw_state(const unsigned char buf[],
hw
->
right
=
(
buf
[
0
]
&
0x02
)
?
1
:
0
;
}
/* Convert wrap-around values to negative */
if
(
hw
->
x
>
X_MAX_POSITIVE
)
hw
->
x
-=
1
<<
ABS_POS_BITS
;
if
(
hw
->
y
>
Y_MAX_POSITIVE
)
hw
->
y
-=
1
<<
ABS_POS_BITS
;
return
0
;
}
...
...
drivers/input/tablet/wacom_wac.c
浏览文件 @
cf45b5a2
...
...
@@ -464,7 +464,7 @@ static void wacom_intuos_general(struct wacom_wac *wacom)
t
=
(
data
[
6
]
<<
2
)
|
((
data
[
7
]
>>
6
)
&
3
);
if
((
features
->
type
>=
INTUOS4S
&&
features
->
type
<=
INTUOS4L
)
||
(
features
->
type
>=
INTUOS5S
&&
features
->
type
<=
INTUOS5L
)
||
features
->
type
==
WACOM_21UX2
||
features
->
type
==
WACOM_24HD
)
{
(
features
->
type
>=
WACOM_21UX2
&&
features
->
type
<=
WACOM_24HD
)
)
{
t
=
(
t
<<
1
)
|
(
data
[
1
]
&
1
);
}
input_report_abs
(
input
,
ABS_PRESSURE
,
t
);
...
...
@@ -614,7 +614,7 @@ static int wacom_intuos_irq(struct wacom_wac *wacom)
input_report_abs
(
input
,
ABS_MISC
,
0
);
}
}
else
{
if
(
features
->
type
==
WACOM_21UX2
)
{
if
(
features
->
type
==
WACOM_21UX2
||
features
->
type
==
WACOM_22HD
)
{
input_report_key
(
input
,
BTN_0
,
(
data
[
5
]
&
0x01
));
input_report_key
(
input
,
BTN_1
,
(
data
[
6
]
&
0x01
));
input_report_key
(
input
,
BTN_2
,
(
data
[
6
]
&
0x02
));
...
...
@@ -633,6 +633,12 @@ static int wacom_intuos_irq(struct wacom_wac *wacom)
input_report_key
(
input
,
BTN_Z
,
(
data
[
8
]
&
0x20
));
input_report_key
(
input
,
BTN_BASE
,
(
data
[
8
]
&
0x40
));
input_report_key
(
input
,
BTN_BASE2
,
(
data
[
8
]
&
0x80
));
if
(
features
->
type
==
WACOM_22HD
)
{
input_report_key
(
input
,
KEY_PROG1
,
data
[
9
]
&
0x01
);
input_report_key
(
input
,
KEY_PROG2
,
data
[
9
]
&
0x02
);
input_report_key
(
input
,
KEY_PROG3
,
data
[
9
]
&
0x04
);
}
}
else
{
input_report_key
(
input
,
BTN_0
,
(
data
[
5
]
&
0x01
));
input_report_key
(
input
,
BTN_1
,
(
data
[
5
]
&
0x02
));
...
...
@@ -1231,6 +1237,7 @@ void wacom_wac_irq(struct wacom_wac *wacom_wac, size_t len)
case
CINTIQ
:
case
WACOM_BEE
:
case
WACOM_21UX2
:
case
WACOM_22HD
:
case
WACOM_24HD
:
sync
=
wacom_intuos_irq
(
wacom_wac
);
break
;
...
...
@@ -1432,6 +1439,12 @@ int wacom_setup_input_capabilities(struct input_dev *input_dev,
wacom_setup_cintiq
(
wacom_wac
);
break
;
case
WACOM_22HD
:
__set_bit
(
KEY_PROG1
,
input_dev
->
keybit
);
__set_bit
(
KEY_PROG2
,
input_dev
->
keybit
);
__set_bit
(
KEY_PROG3
,
input_dev
->
keybit
);
/* fall through */
case
WACOM_21UX2
:
__set_bit
(
BTN_A
,
input_dev
->
keybit
);
__set_bit
(
BTN_B
,
input_dev
->
keybit
);
...
...
@@ -1858,6 +1871,9 @@ static const struct wacom_features wacom_features_0xF0 =
static
const
struct
wacom_features
wacom_features_0xCC
=
{
"Wacom Cintiq 21UX2"
,
WACOM_PKGLEN_INTUOS
,
87200
,
65600
,
2047
,
63
,
WACOM_21UX2
,
WACOM_INTUOS3_RES
,
WACOM_INTUOS3_RES
};
static
const
struct
wacom_features
wacom_features_0xFA
=
{
"Wacom Cintiq 22HD"
,
WACOM_PKGLEN_INTUOS
,
95840
,
54260
,
2047
,
63
,
WACOM_22HD
,
WACOM_INTUOS3_RES
,
WACOM_INTUOS3_RES
};
static
const
struct
wacom_features
wacom_features_0x90
=
{
"Wacom ISDv4 90"
,
WACOM_PKGLEN_GRAPHIRE
,
26202
,
16325
,
255
,
0
,
TABLETPC
,
WACOM_INTUOS_RES
,
WACOM_INTUOS_RES
};
...
...
@@ -2075,6 +2091,7 @@ const struct usb_device_id wacom_ids[] = {
{
USB_DEVICE_WACOM
(
0xEF
)
},
{
USB_DEVICE_WACOM
(
0x47
)
},
{
USB_DEVICE_WACOM
(
0xF4
)
},
{
USB_DEVICE_WACOM
(
0xFA
)
},
{
USB_DEVICE_LENOVO
(
0x6004
)
},
{
}
};
...
...
drivers/input/tablet/wacom_wac.h
浏览文件 @
cf45b5a2
...
...
@@ -73,8 +73,9 @@ enum {
INTUOS5S
,
INTUOS5
,
INTUOS5L
,
WACOM_24HD
,
WACOM_21UX2
,
WACOM_22HD
,
WACOM_24HD
,
CINTIQ
,
WACOM_BEE
,
WACOM_MO
,
...
...
drivers/input/touchscreen/Kconfig
浏览文件 @
cf45b5a2
...
...
@@ -472,6 +472,19 @@ config TOUCHSCREEN_PENMOUNT
To compile this driver as a module, choose M here: the
module will be called penmount.
config TOUCHSCREEN_EDT_FT5X06
tristate "EDT FocalTech FT5x06 I2C Touchscreen support"
depends on I2C
help
Say Y here if you have an EDT "Polytouch" touchscreen based
on the FocalTech FT5x06 family of controllers connected to
your system.
If unsure, say N.
To compile this driver as a module, choose M here: the
module will be called edt-ft5x06.
config TOUCHSCREEN_MIGOR
tristate "Renesas MIGO-R touchscreen"
depends on SH_MIGOR && I2C
...
...
drivers/input/touchscreen/Makefile
浏览文件 @
cf45b5a2
...
...
@@ -24,6 +24,7 @@ obj-$(CONFIG_TOUCHSCREEN_CYTTSP_SPI) += cyttsp_spi.o
obj-$(CONFIG_TOUCHSCREEN_DA9034)
+=
da9034-ts.o
obj-$(CONFIG_TOUCHSCREEN_DA9052)
+=
da9052_tsi.o
obj-$(CONFIG_TOUCHSCREEN_DYNAPRO)
+=
dynapro.o
obj-$(CONFIG_TOUCHSCREEN_EDT_FT5X06)
+=
edt-ft5x06.o
obj-$(CONFIG_TOUCHSCREEN_HAMPSHIRE)
+=
hampshire.o
obj-$(CONFIG_TOUCHSCREEN_GUNZE)
+=
gunze.o
obj-$(CONFIG_TOUCHSCREEN_EETI)
+=
eeti_ts.o
...
...
drivers/input/touchscreen/edt-ft5x06.c
0 → 100644
浏览文件 @
cf45b5a2
/*
* Copyright (C) 2012 Simon Budig, <simon.budig@kernelconcepts.de>
*
* This software is licensed under the terms of the GNU General Public
* License version 2, as published by the Free Software Foundation, and
* may be copied, distributed, and modified under those terms.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
/*
* This is a driver for the EDT "Polytouch" family of touch controllers
* based on the FocalTech FT5x06 line of chips.
*
* Development of this driver has been sponsored by Glyn:
* http://www.glyn.com/Products/Displays
*/
#include <linux/module.h>
#include <linux/ratelimit.h>
#include <linux/interrupt.h>
#include <linux/input.h>
#include <linux/i2c.h>
#include <linux/uaccess.h>
#include <linux/delay.h>
#include <linux/debugfs.h>
#include <linux/slab.h>
#include <linux/gpio.h>
#include <linux/input/mt.h>
#include <linux/input/edt-ft5x06.h>
#define MAX_SUPPORT_POINTS 5
#define WORK_REGISTER_THRESHOLD 0x00
#define WORK_REGISTER_REPORT_RATE 0x08
#define WORK_REGISTER_GAIN 0x30
#define WORK_REGISTER_OFFSET 0x31
#define WORK_REGISTER_NUM_X 0x33
#define WORK_REGISTER_NUM_Y 0x34
#define WORK_REGISTER_OPMODE 0x3c
#define FACTORY_REGISTER_OPMODE 0x01
#define TOUCH_EVENT_DOWN 0x00
#define TOUCH_EVENT_UP 0x01
#define TOUCH_EVENT_ON 0x02
#define TOUCH_EVENT_RESERVED 0x03
#define EDT_NAME_LEN 23
#define EDT_SWITCH_MODE_RETRIES 10
#define EDT_SWITCH_MODE_DELAY 5
/* msec */
#define EDT_RAW_DATA_RETRIES 100
#define EDT_RAW_DATA_DELAY 1
/* msec */
struct
edt_ft5x06_ts_data
{
struct
i2c_client
*
client
;
struct
input_dev
*
input
;
u16
num_x
;
u16
num_y
;
#if defined(CONFIG_DEBUG_FS)
struct
dentry
*
debug_dir
;
u8
*
raw_buffer
;
size_t
raw_bufsize
;
#endif
struct
mutex
mutex
;
bool
factory_mode
;
int
threshold
;
int
gain
;
int
offset
;
int
report_rate
;
char
name
[
EDT_NAME_LEN
];
};
static
int
edt_ft5x06_ts_readwrite
(
struct
i2c_client
*
client
,
u16
wr_len
,
u8
*
wr_buf
,
u16
rd_len
,
u8
*
rd_buf
)
{
struct
i2c_msg
wrmsg
[
2
];
int
i
=
0
;
int
ret
;
if
(
wr_len
)
{
wrmsg
[
i
].
addr
=
client
->
addr
;
wrmsg
[
i
].
flags
=
0
;
wrmsg
[
i
].
len
=
wr_len
;
wrmsg
[
i
].
buf
=
wr_buf
;
i
++
;
}
if
(
rd_len
)
{
wrmsg
[
i
].
addr
=
client
->
addr
;
wrmsg
[
i
].
flags
=
I2C_M_RD
;
wrmsg
[
i
].
len
=
rd_len
;
wrmsg
[
i
].
buf
=
rd_buf
;
i
++
;
}
ret
=
i2c_transfer
(
client
->
adapter
,
wrmsg
,
i
);
if
(
ret
<
0
)
return
ret
;
if
(
ret
!=
i
)
return
-
EIO
;
return
0
;
}
static
bool
edt_ft5x06_ts_check_crc
(
struct
edt_ft5x06_ts_data
*
tsdata
,
u8
*
buf
,
int
buflen
)
{
int
i
;
u8
crc
=
0
;
for
(
i
=
0
;
i
<
buflen
-
1
;
i
++
)
crc
^=
buf
[
i
];
if
(
crc
!=
buf
[
buflen
-
1
])
{
dev_err_ratelimited
(
&
tsdata
->
client
->
dev
,
"crc error: 0x%02x expected, got 0x%02x
\n
"
,
crc
,
buf
[
buflen
-
1
]);
return
false
;
}
return
true
;
}
static
irqreturn_t
edt_ft5x06_ts_isr
(
int
irq
,
void
*
dev_id
)
{
struct
edt_ft5x06_ts_data
*
tsdata
=
dev_id
;
struct
device
*
dev
=
&
tsdata
->
client
->
dev
;
u8
cmd
=
0xf9
;
u8
rdbuf
[
26
];
int
i
,
type
,
x
,
y
,
id
;
int
error
;
memset
(
rdbuf
,
0
,
sizeof
(
rdbuf
));
error
=
edt_ft5x06_ts_readwrite
(
tsdata
->
client
,
sizeof
(
cmd
),
&
cmd
,
sizeof
(
rdbuf
),
rdbuf
);
if
(
error
)
{
dev_err_ratelimited
(
dev
,
"Unable to fetch data, error: %d
\n
"
,
error
);
goto
out
;
}
if
(
rdbuf
[
0
]
!=
0xaa
||
rdbuf
[
1
]
!=
0xaa
||
rdbuf
[
2
]
!=
26
)
{
dev_err_ratelimited
(
dev
,
"Unexpected header: %02x%02x%02x!
\n
"
,
rdbuf
[
0
],
rdbuf
[
1
],
rdbuf
[
2
]);
goto
out
;
}
if
(
!
edt_ft5x06_ts_check_crc
(
tsdata
,
rdbuf
,
26
))
goto
out
;
for
(
i
=
0
;
i
<
MAX_SUPPORT_POINTS
;
i
++
)
{
u8
*
buf
=
&
rdbuf
[
i
*
4
+
5
];
bool
down
;
type
=
buf
[
0
]
>>
6
;
/* ignore Reserved events */
if
(
type
==
TOUCH_EVENT_RESERVED
)
continue
;
x
=
((
buf
[
0
]
<<
8
)
|
buf
[
1
])
&
0x0fff
;
y
=
((
buf
[
2
]
<<
8
)
|
buf
[
3
])
&
0x0fff
;
id
=
(
buf
[
2
]
>>
4
)
&
0x0f
;
down
=
(
type
!=
TOUCH_EVENT_UP
);
input_mt_slot
(
tsdata
->
input
,
id
);
input_mt_report_slot_state
(
tsdata
->
input
,
MT_TOOL_FINGER
,
down
);
if
(
!
down
)
continue
;
input_report_abs
(
tsdata
->
input
,
ABS_MT_POSITION_X
,
x
);
input_report_abs
(
tsdata
->
input
,
ABS_MT_POSITION_Y
,
y
);
}
input_mt_report_pointer_emulation
(
tsdata
->
input
,
true
);
input_sync
(
tsdata
->
input
);
out:
return
IRQ_HANDLED
;
}
static
int
edt_ft5x06_register_write
(
struct
edt_ft5x06_ts_data
*
tsdata
,
u8
addr
,
u8
value
)
{
u8
wrbuf
[
4
];
wrbuf
[
0
]
=
tsdata
->
factory_mode
?
0xf3
:
0xfc
;
wrbuf
[
1
]
=
tsdata
->
factory_mode
?
addr
&
0x7f
:
addr
&
0x3f
;
wrbuf
[
2
]
=
value
;
wrbuf
[
3
]
=
wrbuf
[
0
]
^
wrbuf
[
1
]
^
wrbuf
[
2
];
return
edt_ft5x06_ts_readwrite
(
tsdata
->
client
,
4
,
wrbuf
,
0
,
NULL
);
}
static
int
edt_ft5x06_register_read
(
struct
edt_ft5x06_ts_data
*
tsdata
,
u8
addr
)
{
u8
wrbuf
[
2
],
rdbuf
[
2
];
int
error
;
wrbuf
[
0
]
=
tsdata
->
factory_mode
?
0xf3
:
0xfc
;
wrbuf
[
1
]
=
tsdata
->
factory_mode
?
addr
&
0x7f
:
addr
&
0x3f
;
wrbuf
[
1
]
|=
tsdata
->
factory_mode
?
0x80
:
0x40
;
error
=
edt_ft5x06_ts_readwrite
(
tsdata
->
client
,
2
,
wrbuf
,
2
,
rdbuf
);
if
(
error
)
return
error
;
if
((
wrbuf
[
0
]
^
wrbuf
[
1
]
^
rdbuf
[
0
])
!=
rdbuf
[
1
])
{
dev_err
(
&
tsdata
->
client
->
dev
,
"crc error: 0x%02x expected, got 0x%02x
\n
"
,
wrbuf
[
0
]
^
wrbuf
[
1
]
^
rdbuf
[
0
],
rdbuf
[
1
]);
return
-
EIO
;
}
return
rdbuf
[
0
];
}
struct
edt_ft5x06_attribute
{
struct
device_attribute
dattr
;
size_t
field_offset
;
u8
limit_low
;
u8
limit_high
;
u8
addr
;
};
#define EDT_ATTR(_field, _mode, _addr, _limit_low, _limit_high) \
struct edt_ft5x06_attribute edt_ft5x06_attr_##_field = { \
.dattr = __ATTR(_field, _mode, \
edt_ft5x06_setting_show, \
edt_ft5x06_setting_store), \
.field_offset = \
offsetof(struct edt_ft5x06_ts_data, _field), \
.limit_low = _limit_low, \
.limit_high = _limit_high, \
.addr = _addr, \
}
static
ssize_t
edt_ft5x06_setting_show
(
struct
device
*
dev
,
struct
device_attribute
*
dattr
,
char
*
buf
)
{
struct
i2c_client
*
client
=
to_i2c_client
(
dev
);
struct
edt_ft5x06_ts_data
*
tsdata
=
i2c_get_clientdata
(
client
);
struct
edt_ft5x06_attribute
*
attr
=
container_of
(
dattr
,
struct
edt_ft5x06_attribute
,
dattr
);
u8
*
field
=
(
u8
*
)((
char
*
)
tsdata
+
attr
->
field_offset
);
int
val
;
size_t
count
=
0
;
int
error
=
0
;
mutex_lock
(
&
tsdata
->
mutex
);
if
(
tsdata
->
factory_mode
)
{
error
=
-
EIO
;
goto
out
;
}
val
=
edt_ft5x06_register_read
(
tsdata
,
attr
->
addr
);
if
(
val
<
0
)
{
error
=
val
;
dev_err
(
&
tsdata
->
client
->
dev
,
"Failed to fetch attribute %s, error %d
\n
"
,
dattr
->
attr
.
name
,
error
);
goto
out
;
}
if
(
val
!=
*
field
)
{
dev_warn
(
&
tsdata
->
client
->
dev
,
"%s: read (%d) and stored value (%d) differ
\n
"
,
dattr
->
attr
.
name
,
val
,
*
field
);
*
field
=
val
;
}
count
=
scnprintf
(
buf
,
PAGE_SIZE
,
"%d
\n
"
,
val
);
out:
mutex_unlock
(
&
tsdata
->
mutex
);
return
error
?:
count
;
}
static
ssize_t
edt_ft5x06_setting_store
(
struct
device
*
dev
,
struct
device_attribute
*
dattr
,
const
char
*
buf
,
size_t
count
)
{
struct
i2c_client
*
client
=
to_i2c_client
(
dev
);
struct
edt_ft5x06_ts_data
*
tsdata
=
i2c_get_clientdata
(
client
);
struct
edt_ft5x06_attribute
*
attr
=
container_of
(
dattr
,
struct
edt_ft5x06_attribute
,
dattr
);
u8
*
field
=
(
u8
*
)((
char
*
)
tsdata
+
attr
->
field_offset
);
unsigned
int
val
;
int
error
;
mutex_lock
(
&
tsdata
->
mutex
);
if
(
tsdata
->
factory_mode
)
{
error
=
-
EIO
;
goto
out
;
}
error
=
kstrtouint
(
buf
,
0
,
&
val
);
if
(
error
)
goto
out
;
if
(
val
<
attr
->
limit_low
||
val
>
attr
->
limit_high
)
{
error
=
-
ERANGE
;
goto
out
;
}
error
=
edt_ft5x06_register_write
(
tsdata
,
attr
->
addr
,
val
);
if
(
error
)
{
dev_err
(
&
tsdata
->
client
->
dev
,
"Failed to update attribute %s, error: %d
\n
"
,
dattr
->
attr
.
name
,
error
);
goto
out
;
}
*
field
=
val
;
out:
mutex_unlock
(
&
tsdata
->
mutex
);
return
error
?:
count
;
}
static
EDT_ATTR
(
gain
,
S_IWUSR
|
S_IRUGO
,
WORK_REGISTER_GAIN
,
0
,
31
);
static
EDT_ATTR
(
offset
,
S_IWUSR
|
S_IRUGO
,
WORK_REGISTER_OFFSET
,
0
,
31
);
static
EDT_ATTR
(
threshold
,
S_IWUSR
|
S_IRUGO
,
WORK_REGISTER_THRESHOLD
,
20
,
80
);
static
EDT_ATTR
(
report_rate
,
S_IWUSR
|
S_IRUGO
,
WORK_REGISTER_REPORT_RATE
,
3
,
14
);
static
struct
attribute
*
edt_ft5x06_attrs
[]
=
{
&
edt_ft5x06_attr_gain
.
dattr
.
attr
,
&
edt_ft5x06_attr_offset
.
dattr
.
attr
,
&
edt_ft5x06_attr_threshold
.
dattr
.
attr
,
&
edt_ft5x06_attr_report_rate
.
dattr
.
attr
,
NULL
};
static
const
struct
attribute_group
edt_ft5x06_attr_group
=
{
.
attrs
=
edt_ft5x06_attrs
,
};
#ifdef CONFIG_DEBUG_FS
static
int
edt_ft5x06_factory_mode
(
struct
edt_ft5x06_ts_data
*
tsdata
)
{
struct
i2c_client
*
client
=
tsdata
->
client
;
int
retries
=
EDT_SWITCH_MODE_RETRIES
;
int
ret
;
int
error
;
disable_irq
(
client
->
irq
);
if
(
!
tsdata
->
raw_buffer
)
{
tsdata
->
raw_bufsize
=
tsdata
->
num_x
*
tsdata
->
num_y
*
sizeof
(
u16
);
tsdata
->
raw_buffer
=
kzalloc
(
tsdata
->
raw_bufsize
,
GFP_KERNEL
);
if
(
!
tsdata
->
raw_buffer
)
{
error
=
-
ENOMEM
;
goto
err_out
;
}
}
/* mode register is 0x3c when in the work mode */
error
=
edt_ft5x06_register_write
(
tsdata
,
WORK_REGISTER_OPMODE
,
0x03
);
if
(
error
)
{
dev_err
(
&
client
->
dev
,
"failed to switch to factory mode, error %d
\n
"
,
error
);
goto
err_out
;
}
tsdata
->
factory_mode
=
true
;
do
{
mdelay
(
EDT_SWITCH_MODE_DELAY
);
/* mode register is 0x01 when in factory mode */
ret
=
edt_ft5x06_register_read
(
tsdata
,
FACTORY_REGISTER_OPMODE
);
if
(
ret
==
0x03
)
break
;
}
while
(
--
retries
>
0
);
if
(
retries
==
0
)
{
dev_err
(
&
client
->
dev
,
"not in factory mode after %dms.
\n
"
,
EDT_SWITCH_MODE_RETRIES
*
EDT_SWITCH_MODE_DELAY
);
error
=
-
EIO
;
goto
err_out
;
}
return
0
;
err_out:
kfree
(
tsdata
->
raw_buffer
);
tsdata
->
raw_buffer
=
NULL
;
tsdata
->
factory_mode
=
false
;
enable_irq
(
client
->
irq
);
return
error
;
}
static
int
edt_ft5x06_work_mode
(
struct
edt_ft5x06_ts_data
*
tsdata
)
{
struct
i2c_client
*
client
=
tsdata
->
client
;
int
retries
=
EDT_SWITCH_MODE_RETRIES
;
int
ret
;
int
error
;
/* mode register is 0x01 when in the factory mode */
error
=
edt_ft5x06_register_write
(
tsdata
,
FACTORY_REGISTER_OPMODE
,
0x1
);
if
(
error
)
{
dev_err
(
&
client
->
dev
,
"failed to switch to work mode, error: %d
\n
"
,
error
);
return
error
;
}
tsdata
->
factory_mode
=
false
;
do
{
mdelay
(
EDT_SWITCH_MODE_DELAY
);
/* mode register is 0x01 when in factory mode */
ret
=
edt_ft5x06_register_read
(
tsdata
,
WORK_REGISTER_OPMODE
);
if
(
ret
==
0x01
)
break
;
}
while
(
--
retries
>
0
);
if
(
retries
==
0
)
{
dev_err
(
&
client
->
dev
,
"not in work mode after %dms.
\n
"
,
EDT_SWITCH_MODE_RETRIES
*
EDT_SWITCH_MODE_DELAY
);
tsdata
->
factory_mode
=
true
;
return
-
EIO
;
}
if
(
tsdata
->
raw_buffer
)
kfree
(
tsdata
->
raw_buffer
);
tsdata
->
raw_buffer
=
NULL
;
/* restore parameters */
edt_ft5x06_register_write
(
tsdata
,
WORK_REGISTER_THRESHOLD
,
tsdata
->
threshold
);
edt_ft5x06_register_write
(
tsdata
,
WORK_REGISTER_GAIN
,
tsdata
->
gain
);
edt_ft5x06_register_write
(
tsdata
,
WORK_REGISTER_OFFSET
,
tsdata
->
offset
);
edt_ft5x06_register_write
(
tsdata
,
WORK_REGISTER_REPORT_RATE
,
tsdata
->
report_rate
);
enable_irq
(
client
->
irq
);
return
0
;
}
static
int
edt_ft5x06_debugfs_mode_get
(
void
*
data
,
u64
*
mode
)
{
struct
edt_ft5x06_ts_data
*
tsdata
=
data
;
*
mode
=
tsdata
->
factory_mode
;
return
0
;
};
static
int
edt_ft5x06_debugfs_mode_set
(
void
*
data
,
u64
mode
)
{
struct
edt_ft5x06_ts_data
*
tsdata
=
data
;
int
retval
=
0
;
if
(
mode
>
1
)
return
-
ERANGE
;
mutex_lock
(
&
tsdata
->
mutex
);
if
(
mode
!=
tsdata
->
factory_mode
)
{
retval
=
mode
?
edt_ft5x06_factory_mode
(
tsdata
)
:
edt_ft5x06_work_mode
(
tsdata
);
}
mutex_unlock
(
&
tsdata
->
mutex
);
return
retval
;
};
DEFINE_SIMPLE_ATTRIBUTE
(
debugfs_mode_fops
,
edt_ft5x06_debugfs_mode_get
,
edt_ft5x06_debugfs_mode_set
,
"%llu
\n
"
);
static
int
edt_ft5x06_debugfs_raw_data_open
(
struct
inode
*
inode
,
struct
file
*
file
)
{
file
->
private_data
=
inode
->
i_private
;
return
0
;
}
static
ssize_t
edt_ft5x06_debugfs_raw_data_read
(
struct
file
*
file
,
char
__user
*
buf
,
size_t
count
,
loff_t
*
off
)
{
struct
edt_ft5x06_ts_data
*
tsdata
=
file
->
private_data
;
struct
i2c_client
*
client
=
tsdata
->
client
;
int
retries
=
EDT_RAW_DATA_RETRIES
;
int
val
,
i
,
error
;
size_t
read
=
0
;
int
colbytes
;
char
wrbuf
[
3
];
u8
*
rdbuf
;
if
(
*
off
<
0
||
*
off
>=
tsdata
->
raw_bufsize
)
return
0
;
mutex_lock
(
&
tsdata
->
mutex
);
if
(
!
tsdata
->
factory_mode
||
!
tsdata
->
raw_buffer
)
{
error
=
-
EIO
;
goto
out
;
}
error
=
edt_ft5x06_register_write
(
tsdata
,
0x08
,
0x01
);
if
(
error
)
{
dev_dbg
(
&
client
->
dev
,
"failed to write 0x08 register, error %d
\n
"
,
error
);
goto
out
;
}
do
{
msleep
(
EDT_RAW_DATA_DELAY
);
val
=
edt_ft5x06_register_read
(
tsdata
,
0x08
);
if
(
val
<
1
)
break
;
}
while
(
--
retries
>
0
);
if
(
val
<
0
)
{
error
=
val
;
dev_dbg
(
&
client
->
dev
,
"failed to read 0x08 register, error %d
\n
"
,
error
);
goto
out
;
}
if
(
retries
==
0
)
{
dev_dbg
(
&
client
->
dev
,
"timed out waiting for register to settle
\n
"
);
error
=
-
ETIMEDOUT
;
goto
out
;
}
rdbuf
=
tsdata
->
raw_buffer
;
colbytes
=
tsdata
->
num_y
*
sizeof
(
u16
);
wrbuf
[
0
]
=
0xf5
;
wrbuf
[
1
]
=
0x0e
;
for
(
i
=
0
;
i
<
tsdata
->
num_x
;
i
++
)
{
wrbuf
[
2
]
=
i
;
/* column index */
error
=
edt_ft5x06_ts_readwrite
(
tsdata
->
client
,
sizeof
(
wrbuf
),
wrbuf
,
colbytes
,
rdbuf
);
if
(
error
)
goto
out
;
rdbuf
+=
colbytes
;
}
read
=
min_t
(
size_t
,
count
,
tsdata
->
raw_bufsize
-
*
off
);
error
=
copy_to_user
(
buf
,
tsdata
->
raw_buffer
+
*
off
,
read
);
if
(
!
error
)
*
off
+=
read
;
out:
mutex_unlock
(
&
tsdata
->
mutex
);
return
error
?:
read
;
};
static
const
struct
file_operations
debugfs_raw_data_fops
=
{
.
open
=
edt_ft5x06_debugfs_raw_data_open
,
.
read
=
edt_ft5x06_debugfs_raw_data_read
,
};
static
void
__devinit
edt_ft5x06_ts_prepare_debugfs
(
struct
edt_ft5x06_ts_data
*
tsdata
,
const
char
*
debugfs_name
)
{
tsdata
->
debug_dir
=
debugfs_create_dir
(
debugfs_name
,
NULL
);
if
(
!
tsdata
->
debug_dir
)
return
;
debugfs_create_u16
(
"num_x"
,
S_IRUSR
,
tsdata
->
debug_dir
,
&
tsdata
->
num_x
);
debugfs_create_u16
(
"num_y"
,
S_IRUSR
,
tsdata
->
debug_dir
,
&
tsdata
->
num_y
);
debugfs_create_file
(
"mode"
,
S_IRUSR
|
S_IWUSR
,
tsdata
->
debug_dir
,
tsdata
,
&
debugfs_mode_fops
);
debugfs_create_file
(
"raw_data"
,
S_IRUSR
,
tsdata
->
debug_dir
,
tsdata
,
&
debugfs_raw_data_fops
);
}
static
void
__devexit
edt_ft5x06_ts_teardown_debugfs
(
struct
edt_ft5x06_ts_data
*
tsdata
)
{
if
(
tsdata
->
debug_dir
)
debugfs_remove_recursive
(
tsdata
->
debug_dir
);
}
#else
static
inline
void
edt_ft5x06_ts_prepare_debugfs
(
struct
edt_ft5x06_ts_data
*
tsdata
,
const
char
*
debugfs_name
)
{
}
static
inline
void
edt_ft5x06_ts_teardown_debugfs
(
struct
edt_ft5x06_ts_data
*
tsdata
)
{
}
#endif
/* CONFIG_DEBUGFS */
static
int
__devinit
edt_ft5x06_ts_reset
(
struct
i2c_client
*
client
,
int
reset_pin
)
{
int
error
;
if
(
gpio_is_valid
(
reset_pin
))
{
/* this pulls reset down, enabling the low active reset */
error
=
gpio_request_one
(
reset_pin
,
GPIOF_OUT_INIT_LOW
,
"edt-ft5x06 reset"
);
if
(
error
)
{
dev_err
(
&
client
->
dev
,
"Failed to request GPIO %d as reset pin, error %d
\n
"
,
reset_pin
,
error
);
return
error
;
}
mdelay
(
50
);
gpio_set_value
(
reset_pin
,
1
);
mdelay
(
100
);
}
return
0
;
}
static
int
__devinit
edt_ft5x06_ts_identify
(
struct
i2c_client
*
client
,
char
*
model_name
,
char
*
fw_version
)
{
u8
rdbuf
[
EDT_NAME_LEN
];
char
*
p
;
int
error
;
error
=
edt_ft5x06_ts_readwrite
(
client
,
1
,
"
\xbb
"
,
EDT_NAME_LEN
-
1
,
rdbuf
);
if
(
error
)
return
error
;
/* remove last '$' end marker */
rdbuf
[
EDT_NAME_LEN
-
1
]
=
'\0'
;
if
(
rdbuf
[
EDT_NAME_LEN
-
2
]
==
'$'
)
rdbuf
[
EDT_NAME_LEN
-
2
]
=
'\0'
;
/* look for Model/Version separator */
p
=
strchr
(
rdbuf
,
'*'
);
if
(
p
)
*
p
++
=
'\0'
;
strlcpy
(
model_name
,
rdbuf
+
1
,
EDT_NAME_LEN
);
strlcpy
(
fw_version
,
p
?
p
:
""
,
EDT_NAME_LEN
);
return
0
;
}
#define EDT_ATTR_CHECKSET(name, reg) \
if (pdata->name >= edt_ft5x06_attr_##name.limit_low && \
pdata->name <= edt_ft5x06_attr_##name.limit_high) \
edt_ft5x06_register_write(tsdata, reg, pdata->name)
static
void
__devinit
edt_ft5x06_ts_get_defaults
(
struct
edt_ft5x06_ts_data
*
tsdata
,
const
struct
edt_ft5x06_platform_data
*
pdata
)
{
if
(
!
pdata
->
use_parameters
)
return
;
/* pick up defaults from the platform data */
EDT_ATTR_CHECKSET
(
threshold
,
WORK_REGISTER_THRESHOLD
);
EDT_ATTR_CHECKSET
(
gain
,
WORK_REGISTER_GAIN
);
EDT_ATTR_CHECKSET
(
offset
,
WORK_REGISTER_OFFSET
);
EDT_ATTR_CHECKSET
(
report_rate
,
WORK_REGISTER_REPORT_RATE
);
}
static
void
__devinit
edt_ft5x06_ts_get_parameters
(
struct
edt_ft5x06_ts_data
*
tsdata
)
{
tsdata
->
threshold
=
edt_ft5x06_register_read
(
tsdata
,
WORK_REGISTER_THRESHOLD
);
tsdata
->
gain
=
edt_ft5x06_register_read
(
tsdata
,
WORK_REGISTER_GAIN
);
tsdata
->
offset
=
edt_ft5x06_register_read
(
tsdata
,
WORK_REGISTER_OFFSET
);
tsdata
->
report_rate
=
edt_ft5x06_register_read
(
tsdata
,
WORK_REGISTER_REPORT_RATE
);
tsdata
->
num_x
=
edt_ft5x06_register_read
(
tsdata
,
WORK_REGISTER_NUM_X
);
tsdata
->
num_y
=
edt_ft5x06_register_read
(
tsdata
,
WORK_REGISTER_NUM_Y
);
}
static
int
__devinit
edt_ft5x06_ts_probe
(
struct
i2c_client
*
client
,
const
struct
i2c_device_id
*
id
)
{
const
struct
edt_ft5x06_platform_data
*
pdata
=
client
->
dev
.
platform_data
;
struct
edt_ft5x06_ts_data
*
tsdata
;
struct
input_dev
*
input
;
int
error
;
char
fw_version
[
EDT_NAME_LEN
];
dev_dbg
(
&
client
->
dev
,
"probing for EDT FT5x06 I2C
\n
"
);
if
(
!
pdata
)
{
dev_err
(
&
client
->
dev
,
"no platform data?
\n
"
);
return
-
EINVAL
;
}
error
=
edt_ft5x06_ts_reset
(
client
,
pdata
->
reset_pin
);
if
(
error
)
return
error
;
if
(
gpio_is_valid
(
pdata
->
irq_pin
))
{
error
=
gpio_request_one
(
pdata
->
irq_pin
,
GPIOF_IN
,
"edt-ft5x06 irq"
);
if
(
error
)
{
dev_err
(
&
client
->
dev
,
"Failed to request GPIO %d, error %d
\n
"
,
pdata
->
irq_pin
,
error
);
return
error
;
}
}
tsdata
=
kzalloc
(
sizeof
(
*
tsdata
),
GFP_KERNEL
);
input
=
input_allocate_device
();
if
(
!
tsdata
||
!
input
)
{
dev_err
(
&
client
->
dev
,
"failed to allocate driver data.
\n
"
);
error
=
-
ENOMEM
;
goto
err_free_mem
;
}
mutex_init
(
&
tsdata
->
mutex
);
tsdata
->
client
=
client
;
tsdata
->
input
=
input
;
tsdata
->
factory_mode
=
false
;
error
=
edt_ft5x06_ts_identify
(
client
,
tsdata
->
name
,
fw_version
);
if
(
error
)
{
dev_err
(
&
client
->
dev
,
"touchscreen probe failed
\n
"
);
goto
err_free_mem
;
}
edt_ft5x06_ts_get_defaults
(
tsdata
,
pdata
);
edt_ft5x06_ts_get_parameters
(
tsdata
);
dev_dbg
(
&
client
->
dev
,
"Model
\"
%s
\"
, Rev.
\"
%s
\"
, %dx%d sensors
\n
"
,
tsdata
->
name
,
fw_version
,
tsdata
->
num_x
,
tsdata
->
num_y
);
input
->
name
=
tsdata
->
name
;
input
->
id
.
bustype
=
BUS_I2C
;
input
->
dev
.
parent
=
&
client
->
dev
;
__set_bit
(
EV_SYN
,
input
->
evbit
);
__set_bit
(
EV_KEY
,
input
->
evbit
);
__set_bit
(
EV_ABS
,
input
->
evbit
);
__set_bit
(
BTN_TOUCH
,
input
->
keybit
);
input_set_abs_params
(
input
,
ABS_X
,
0
,
tsdata
->
num_x
*
64
-
1
,
0
,
0
);
input_set_abs_params
(
input
,
ABS_Y
,
0
,
tsdata
->
num_y
*
64
-
1
,
0
,
0
);
input_set_abs_params
(
input
,
ABS_MT_POSITION_X
,
0
,
tsdata
->
num_x
*
64
-
1
,
0
,
0
);
input_set_abs_params
(
input
,
ABS_MT_POSITION_Y
,
0
,
tsdata
->
num_y
*
64
-
1
,
0
,
0
);
error
=
input_mt_init_slots
(
input
,
MAX_SUPPORT_POINTS
);
if
(
error
)
{
dev_err
(
&
client
->
dev
,
"Unable to init MT slots.
\n
"
);
goto
err_free_mem
;
}
input_set_drvdata
(
input
,
tsdata
);
i2c_set_clientdata
(
client
,
tsdata
);
error
=
request_threaded_irq
(
client
->
irq
,
NULL
,
edt_ft5x06_ts_isr
,
IRQF_TRIGGER_FALLING
|
IRQF_ONESHOT
,
client
->
name
,
tsdata
);
if
(
error
)
{
dev_err
(
&
client
->
dev
,
"Unable to request touchscreen IRQ.
\n
"
);
goto
err_free_mem
;
}
error
=
sysfs_create_group
(
&
client
->
dev
.
kobj
,
&
edt_ft5x06_attr_group
);
if
(
error
)
goto
err_free_irq
;
error
=
input_register_device
(
input
);
if
(
error
)
goto
err_remove_attrs
;
edt_ft5x06_ts_prepare_debugfs
(
tsdata
,
dev_driver_string
(
&
client
->
dev
));
device_init_wakeup
(
&
client
->
dev
,
1
);
dev_dbg
(
&
client
->
dev
,
"EDT FT5x06 initialized: IRQ pin %d, Reset pin %d.
\n
"
,
pdata
->
irq_pin
,
pdata
->
reset_pin
);
return
0
;
err_remove_attrs:
sysfs_remove_group
(
&
client
->
dev
.
kobj
,
&
edt_ft5x06_attr_group
);
err_free_irq:
free_irq
(
client
->
irq
,
tsdata
);
err_free_mem:
input_free_device
(
input
);
kfree
(
tsdata
);
if
(
gpio_is_valid
(
pdata
->
irq_pin
))
gpio_free
(
pdata
->
irq_pin
);
return
error
;
}
static
int
__devexit
edt_ft5x06_ts_remove
(
struct
i2c_client
*
client
)
{
const
struct
edt_ft5x06_platform_data
*
pdata
=
dev_get_platdata
(
&
client
->
dev
);
struct
edt_ft5x06_ts_data
*
tsdata
=
i2c_get_clientdata
(
client
);
edt_ft5x06_ts_teardown_debugfs
(
tsdata
);
sysfs_remove_group
(
&
client
->
dev
.
kobj
,
&
edt_ft5x06_attr_group
);
free_irq
(
client
->
irq
,
tsdata
);
input_unregister_device
(
tsdata
->
input
);
if
(
gpio_is_valid
(
pdata
->
irq_pin
))
gpio_free
(
pdata
->
irq_pin
);
if
(
gpio_is_valid
(
pdata
->
reset_pin
))
gpio_free
(
pdata
->
reset_pin
);
kfree
(
tsdata
->
raw_buffer
);
kfree
(
tsdata
);
return
0
;
}
#ifdef CONFIG_PM_SLEEP
static
int
edt_ft5x06_ts_suspend
(
struct
device
*
dev
)
{
struct
i2c_client
*
client
=
to_i2c_client
(
dev
);
if
(
device_may_wakeup
(
dev
))
enable_irq_wake
(
client
->
irq
);
return
0
;
}
static
int
edt_ft5x06_ts_resume
(
struct
device
*
dev
)
{
struct
i2c_client
*
client
=
to_i2c_client
(
dev
);
if
(
device_may_wakeup
(
dev
))
disable_irq_wake
(
client
->
irq
);
return
0
;
}
#endif
static
SIMPLE_DEV_PM_OPS
(
edt_ft5x06_ts_pm_ops
,
edt_ft5x06_ts_suspend
,
edt_ft5x06_ts_resume
);
static
const
struct
i2c_device_id
edt_ft5x06_ts_id
[]
=
{
{
"edt-ft5x06"
,
0
},
{
}
};
MODULE_DEVICE_TABLE
(
i2c
,
edt_ft5x06_ts_id
);
static
struct
i2c_driver
edt_ft5x06_ts_driver
=
{
.
driver
=
{
.
owner
=
THIS_MODULE
,
.
name
=
"edt_ft5x06"
,
.
pm
=
&
edt_ft5x06_ts_pm_ops
,
},
.
id_table
=
edt_ft5x06_ts_id
,
.
probe
=
edt_ft5x06_ts_probe
,
.
remove
=
__devexit_p
(
edt_ft5x06_ts_remove
),
};
module_i2c_driver
(
edt_ft5x06_ts_driver
);
MODULE_AUTHOR
(
"Simon Budig <simon.budig@kernelconcepts.de>"
);
MODULE_DESCRIPTION
(
"EDT FT5x06 I2C Touchscreen Driver"
);
MODULE_LICENSE
(
"GPL"
);
include/linux/input/edt-ft5x06.h
0 → 100644
浏览文件 @
cf45b5a2
#ifndef _EDT_FT5X06_H
#define _EDT_FT5X06_H
/*
* Copyright (c) 2012 Simon Budig, <simon.budig@kernelconcepts.de>
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 as published by
* the Free Software Foundation.
*/
struct
edt_ft5x06_platform_data
{
int
irq_pin
;
int
reset_pin
;
/* startup defaults for operational parameters */
bool
use_parameters
;
u8
gain
;
u8
threshold
;
u8
offset
;
u8
report_rate
;
};
#endif
/* _EDT_FT5X06_H */
编辑
预览
Markdown
is supported
0%
请重试
或
添加新附件
.
添加附件
取消
You are about to add
0
people
to the discussion. Proceed with caution.
先完成此消息的编辑!
取消
想要评论请
注册
或
登录