From: "Václav Krpec" <vencik@razdva.cz>
To: Elaine Chen <elaineee66@gmail.com>
Cc: Dmitry Torokhov <dmitry.torokhov@gmail.com>,
Kevin Cernekee <cernekee@gmail.com>,
david turvene <dturvene@dahetral.com>,
linux-input@vger.kernel.org, Niels de Vos <ndevos@redhat.com>,
jclift@redhat.com--cc, Qiting Chen <qiting.chen@cn.alps.com>,
Justin Clift <justin@gluster.org>,
drejc.kopac@fmf.uni-lj.si
Subject: Re: [PATCH] input: add support for ALPS v7 protocol device
Date: Tue, 29 Apr 2014 15:52:29 +0200 [thread overview]
Message-ID: <1398779549.4630.90.camel@adora> (raw)
In-Reply-To: <CAKvfdtKRqaCVJiFQcF2Q+buSRpacLisaMz6eHK9GmXZbvz9bAA@mail.gmail.com>
Hi Elanie,
just a note about the ALPSv7 trackstick code I've suggested
earlier; Drejc (cc'd) reports that it works for his
Toshiba Portege Z30-A-12Q, too.
Best Regards
vencik
On Thu, 2014-03-27 at 15:56 +0800, Elaine Chen wrote:
> Hello Vencik,
>
>
> Thank you for evaluating the patch.
>
>
> 1/ About stickpoint support
>
> Yes, this patch hasn't added stickpoint support for v7 protocol
> device. What's the SP behavior on your Toshiba laptop after
> applying our patch? No function or works abnormally? Please let me
> know the phenomenon as I don't have a v7 TP/SP dual device currently.
>
> I also checked your stickpoint process code. The SP packet decode
> seems doesn't match the format of Specification. Sorry I haven't
> tested it as lack of
>
> device.Did it work on your machine?
>
> I'll get down to support for SP next week I got such a device. And
> will release it with next patch.
>
>
> 2/ This patch is against Dmitry Torokhov's input tree(3.13-rc4)
> https://git.kernel.org/cgit/linux/kernel/git/dtor/input.git/
>
> I've checked the alps.(ch) from 3.13-rc4 and 3.14-rc8, they are the
> same. Maybe there are something unmatch with patch format.
>
> My patch is made from git format-patch.
>
>
>
>
> 2014-03-26 20:20 GMT+08:00 Václav Krpec <vencik@razdva.cz>:
> Hello Qiting,
>
> I've applied your patch and tested the driver on my Toshiba
> Portege
> Z30-A-12N (device ID is 73 03 0a, FW ver: 88 b3 22).
>
> The TP driver works nicely, however, I've observed a few
> things to note:
>
> 1/
> There's no support for trackstick; my device has one.
> Justin has suggested that it may be a Toshiba mod of the
> device...
> Nevertheless, since I was in process of RA of the device
> myself before
> you've committed the patch, I merged the TS driver to your
> patch;
> see alps_process_trackstick_packet_v7 function + tiny bit of
> refactoring of the packet ID resolving mechanism in the patch
> attached.
> I hope it shouldn't break the driver functionality for devices
> w/o
> the trackstick, but testing should definitely be done.
>
> 2/
> I've noticed that your patch wasn't cleanly applicable to
> current 3.14
> kernel; could you be more specific on what branch should it be
> applied?
> The patch attached is valid for 3.14-rc8 tree.
>
> 3/
> I also took the liberty of fixing indentation of your code a
> bit to put
> it (hopefully) more in line with the conventions of the
> alps.[ch]
>
> So, could you (or anyone else) test the patch attached?
> Comments, recommendations etc welcome.
>
> Thanks,
>
> Best regards
>
> vencik
>
>
>
> On Wed, 2014-03-19 at 16:55 +0800, Qiting Chen wrote:
> > Here is a patch of supporting ALPS v7 protocol device.
> > ALPS v7 protocol device is a clickpad that is currently used
> on
> > Lenovo S430/S435/S530, Lenovo Z410/Z510, HP Odie, HP Revolve
> 810 G1,
> > as well as other machines with ALPS Touchpad of following
> infomation:
> > Device ID = 0x73, 0x03, 0x0a
> > Firmware ID = 0x88, 0xb*, 0x**
> >
> > A v7 protocol support patch is first relesed 2 months ago:
> > http://www.spinics.net/lists/linux-input/msg29084.html
> > After that some feedbacks were received from end user. Now
> this patch fixed the bugs
> > reported by them:
> > 1) Fix cursor jump when doing a right click drag
> > 2) Fix cursor jitter when button clicking
> >
> > Signed-off-by: Qiting Chen <qiting.chen@cn.alps.com>
> > ---
> > drivers/input/mouse/alps.c | 560
> ++++++++++++++++++++++++++++++++++++++++++---
> > drivers/input/mouse/alps.h | 132 +++++++++--
> > 2 files changed, 641 insertions(+), 51 deletions(-)
> >
> > diff --git a/drivers/input/mouse/alps.c
> b/drivers/input/mouse/alps.c
> > index fb15c64..383281f 100644
> > --- a/drivers/input/mouse/alps.c
> > +++ b/drivers/input/mouse/alps.c
> > @@ -32,6 +32,13 @@
> > #define ALPS_REG_BASE_RUSHMORE 0xc2c0
> > #define ALPS_REG_BASE_PINNACLE 0x0000
> >
> > +#define LEFT_BUTTON_BIT 0x01
> > +#define RIGHT_BUTTON_BIT 0x02
> > +
> > +#define V7_LARGE_MOVEMENT 130
> > +#define V7_DEAD_ZONE_OFFSET_X 72
> > +#define V7_DEAD_ZONE_OFFSET_Y 72
> > +
> > static const struct alps_nibble_commands
> alps_v3_nibble_commands[] = {
> > { PSMOUSE_CMD_SETPOLL, 0x00 }, /* 0 */
> > { PSMOUSE_CMD_RESET_DIS, 0x00 }, /* 1 */
> > @@ -99,6 +106,7 @@ static const struct alps_nibble_commands
> alps_v6_nibble_commands[] = {
> > #define ALPS_FOUR_BUTTONS 0x40 /* 4 direction button
> present */
> > #define ALPS_PS2_INTERLEAVED 0x80 /* 3-byte PS/2 packet
> interleaved with
> > 6-byte ALPS packet
> */
> > +#define ALPS_BTNLESS 0x100 /* ALPS
> ClickPad flag */
> >
> > static const struct alps_model_info alps_model_data[] = {
> > { { 0x32, 0x02, 0x14 }, 0x00, ALPS_PROTO_V2, 0xf8,
> 0xf8, ALPS_PASS | ALPS_DUALPOINT }, /* Toshiba Salellite Pro
> M10 */
> > @@ -140,6 +148,20 @@ static void
> alps_set_abs_params_mt(struct alps_data *priv,
> > * isn't valid per PS/2 spec.
> > */
> >
> > +static unsigned int alps_pt_distance(struct alps_abs_data
> *pt0,
> > + struct alps_abs_data *pt1)
> > +{
> > + int vect_x, vect_y;
> > +
> > + if (!pt0 || !pt1)
> > + return 0;
> > +
> > + vect_x = pt0->x - pt1->x;
> > + vect_y = pt0->y - pt1->y;
> > +
> > + return int_sqrt(vect_x * vect_x + vect_y * vect_y);
> > +}
> > +
> > /* Packet formats are described in
> Documentation/input/alps.txt */
> >
> > static bool alps_is_valid_first_byte(struct alps_data
> *priv,
> > @@ -320,8 +342,8 @@ static void
> alps_process_bitmap_dolphin(struct alps_data *priv,
> > end_bit = y_msb - 1;
> > box_middle_y = (priv->y_max * (start_bit +
> end_bit)) /
> > (2 * (priv->y_bits - 1));
> > - *x1 = fields->x;
> > - *y1 = fields->y;
> > + *x1 = fields->pt.x;
> > + *y1 = fields->pt.y;
> > *x2 = 2 * box_middle_x - *x1;
> > *y2 = 2 * box_middle_y - *y1;
> > }
> > @@ -461,6 +483,38 @@ static void
> alps_report_semi_mt_data(struct input_dev *dev, int
> num_fingers,
> > alps_set_slot(dev, 1, num_fingers == 2, x2, y2);
> > }
> >
> > +static void alps_report_coord_and_btn(struct psmouse
> *psmouse,
> > + struct alps_fields *f)
> > +{
> > + struct input_dev *dev;
> > +
> > + if (!psmouse || !f)
> > + return;
> > +
> > + dev = psmouse->dev;
> > +
> > + if (f->fingers) {
> > + input_report_key(dev, BTN_TOUCH, 1);
> > + alps_report_semi_mt_data(dev, f->fingers,
> > + f->pt_img[0].x, f->pt_img[0].y,
> > + f->pt_img[1].x, f->pt_img[1].y);
> > + input_mt_report_finger_count(dev, f->fingers);
> > +
> > + input_report_abs(dev, ABS_X, f->pt_img[0].x);
> > + input_report_abs(dev, ABS_Y, f->pt_img[0].y);
> > + input_report_abs(dev, ABS_PRESSURE,
> f->pt_img[0].z);
> > + } else {
> > + input_report_key(dev, BTN_TOUCH, 0);
> > + input_mt_report_finger_count(dev, 0);
> > + input_report_abs(dev, ABS_PRESSURE, 0);
> > + }
> > +
> > + input_report_key(dev, BTN_LEFT, f->btn.left);
> > + input_report_key(dev, BTN_RIGHT, f->btn.right);
> > +
> > + input_sync(dev);
> > +}
> > +
> > static void alps_process_trackstick_packet_v3(struct
> psmouse *psmouse)
> > {
> > struct alps_data *priv = psmouse->private;
> > @@ -523,13 +577,13 @@ static void
> alps_process_trackstick_packet_v3(struct psmouse *psmouse)
> >
> > static void alps_decode_buttons_v3(struct alps_fields *f,
> unsigned char *p)
> > {
> > - f->left = !!(p[3] & 0x01);
> > - f->right = !!(p[3] & 0x02);
> > - f->middle = !!(p[3] & 0x04);
> > + f->btn.left = !!(p[3] & 0x01);
> > + f->btn.right = !!(p[3] & 0x02);
> > + f->btn.middle = !!(p[3] & 0x04);
> >
> > - f->ts_left = !!(p[3] & 0x10);
> > - f->ts_right = !!(p[3] & 0x20);
> > - f->ts_middle = !!(p[3] & 0x40);
> > + f->btn.ts_left = !!(p[3] & 0x10);
> > + f->btn.ts_right = !!(p[3] & 0x20);
> > + f->btn.ts_middle = !!(p[3] & 0x40);
> > }
> >
> > static void alps_decode_pinnacle(struct alps_fields *f,
> unsigned char *p,
> > @@ -546,10 +600,10 @@ static void
> alps_decode_pinnacle(struct alps_fields *f, unsigned char *p,
> > ((p[2] & 0x7f) << 1) |
> > (p[4] & 0x01);
> >
> > - f->x = ((p[1] & 0x7f) << 4) | ((p[4] & 0x30) >> 2) |
> > + f->pt.x = ((p[1] & 0x7f) << 4) | ((p[4] & 0x30) >> 2)
> |
> > ((p[0] & 0x30) >> 4);
> > - f->y = ((p[2] & 0x7f) << 4) | (p[4] & 0x0f);
> > - f->z = p[5] & 0x7f;
> > + f->pt.y = ((p[2] & 0x7f) << 4) | (p[4] & 0x0f);
> > + f->pt.z = p[5] & 0x7f;
> >
> > alps_decode_buttons_v3(f, p);
> > }
> > @@ -573,9 +627,9 @@ static void alps_decode_dolphin(struct
> alps_fields *f, unsigned char *p,
> > f->is_mp = !!(p[0] & 0x20);
> >
> > if (!f->is_mp) {
> > - f->x = ((p[1] & 0x7f) | ((p[4] & 0x0f) << 7));
> > - f->y = ((p[2] & 0x7f) | ((p[4] & 0xf0) << 3));
> > - f->z = (p[0] & 4) ? 0 : p[5] & 0x7f;
> > + f->pt.x = ((p[1] & 0x7f) | ((p[4] & 0x0f) <<
> 7));
> > + f->pt.y = ((p[2] & 0x7f) | ((p[4] & 0xf0) <<
> 3));
> > + f->pt.z = (p[0] & 4) ? 0 : p[5] & 0x7f;
> > alps_decode_buttons_v3(f, p);
> > } else {
> > f->fingers = ((p[0] & 0x6) >> 1 |
> > @@ -687,7 +741,7 @@ static void
> alps_process_touchpad_packet_v3_v5(struct psmouse *psmouse)
> > * with x, y, and z all zero, so these seem to be
> flukes.
> > * Ignore them.
> > */
> > - if (f.x && f.y && !f.z)
> > + if (f.pt.x && f.pt.y && !f.pt.z)
> > return;
> >
> > /*
> > @@ -695,12 +749,12 @@ static void
> alps_process_touchpad_packet_v3_v5(struct psmouse *psmouse)
> > * to rely on ST data.
> > */
> > if (!fingers) {
> > - x1 = f.x;
> > - y1 = f.y;
> > - fingers = f.z > 0 ? 1 : 0;
> > + x1 = f.pt.x;
> > + y1 = f.pt.y;
> > + fingers = f.pt.z > 0 ? 1 : 0;
> > }
> >
> > - if (f.z >= 64)
> > + if (f.pt.z >= 64)
> > input_report_key(dev, BTN_TOUCH, 1);
> > else
> > input_report_key(dev, BTN_TOUCH, 0);
> > @@ -709,22 +763,22 @@ static void
> alps_process_touchpad_packet_v3_v5(struct psmouse *psmouse)
> >
> > input_mt_report_finger_count(dev, fingers);
> >
> > - input_report_key(dev, BTN_LEFT, f.left);
> > - input_report_key(dev, BTN_RIGHT, f.right);
> > - input_report_key(dev, BTN_MIDDLE, f.middle);
> > + input_report_key(dev, BTN_LEFT, f.btn.left);
> > + input_report_key(dev, BTN_RIGHT, f.btn.right);
> > + input_report_key(dev, BTN_MIDDLE, f.btn.middle);
> >
> > - if (f.z > 0) {
> > - input_report_abs(dev, ABS_X, f.x);
> > - input_report_abs(dev, ABS_Y, f.y);
> > + if (f.pt.z > 0) {
> > + input_report_abs(dev, ABS_X, f.pt.x);
> > + input_report_abs(dev, ABS_Y, f.pt.y);
> > }
> > - input_report_abs(dev, ABS_PRESSURE, f.z);
> > + input_report_abs(dev, ABS_PRESSURE, f.pt.z);
> >
> > input_sync(dev);
> >
> > if (!(priv->quirks & ALPS_QUIRK_TRACKSTICK_BUTTONS)) {
> > - input_report_key(dev2, BTN_LEFT, f.ts_left);
> > - input_report_key(dev2, BTN_RIGHT, f.ts_right);
> > - input_report_key(dev2, BTN_MIDDLE,
> f.ts_middle);
> > + input_report_key(dev2, BTN_LEFT,
> f.btn.ts_left);
> > + input_report_key(dev2, BTN_RIGHT,
> f.btn.ts_right);
> > + input_report_key(dev2, BTN_MIDDLE,
> f.btn.ts_middle);
> > input_sync(dev2);
> > }
> > }
> > @@ -916,6 +970,364 @@ static void
> alps_process_packet_v4(struct psmouse *psmouse)
> > input_sync(dev);
> > }
> >
> > +static bool alps_is_valid_package_v7(struct psmouse
> *psmouse)
> > +{
> > + if ((psmouse->pktcnt == 3) && ((psmouse->packet[2] &
> 0x40) != 0x40))
> > + return false;
> > + if ((psmouse->pktcnt == 4) && ((psmouse->packet[3] &
> 0x48) != 0x48))
> > + return false;
> > + if ((psmouse->pktcnt == 6) && ((psmouse->packet[5] &
> 0x40) != 0x0))
> > + return false;
> > + return true;
> > +}
> > +
> > +static int alps_drop_unsupported_packet_v7(struct psmouse
> *psmouse)
> > +{
> > + struct alps_data *priv = psmouse->private;
> > + int drop = 1;
> > +
> > + if (priv->r.v7.pkt_id == V7_PACKET_ID_NEW ||
> > + priv->r.v7.pkt_id == V7_PACKET_ID_TWO ||
> > + priv->r.v7.pkt_id == V7_PACKET_ID_MULTI ||
> > + priv->r.v7.pkt_id == V7_PACKET_ID_IDLE)
> > + drop = 0;
> > +
> > + return drop;
> > +}
> > +
> > +static unsigned char alps_get_packet_id_v7(char *byte)
> > +{
> > + unsigned char packet_id;
> > +
> > + if (byte[4] & 0x40)
> > + packet_id = V7_PACKET_ID_TWO;
> > + else if (byte[4] & 0x01)
> > + packet_id = V7_PACKET_ID_MULTI;
> > + else if ((byte[0] & 0x10) && !(byte[4] & 0x43))
> > + packet_id = V7_PACKET_ID_NEW;
> > + else
> > + packet_id = V7_PACKET_ID_IDLE;
> > +
> > + return packet_id;
> > +}
> > +
> > +static void alps_get_finger_coordinate_v7(struct
> alps_abs_data *pt,
> > + unsigned char *pkt,
> > + unsigned char
> pkt_id)
> > +{
> > + if ((pkt_id == V7_PACKET_ID_TWO) ||
> > + (pkt_id == V7_PACKET_ID_MULTI) ||
> > + (pkt_id == V7_PACKET_ID_NEW)) {
> > + pt[0].x = ((pkt[2] & 0x80) << 4);
> > + pt[0].x |= ((pkt[2] & 0x3F) << 5);
> > + pt[0].x |= ((pkt[3] & 0x30) >> 1);
> > + pt[0].x |= (pkt[3] & 0x07);
> > + pt[0].y = (pkt[1] << 3) | (pkt[0] & 0x07);
> > +
> > + pt[1].x = ((pkt[3] & 0x80) << 4);
> > + pt[1].x |= ((pkt[4] & 0x80) << 3);
> > + pt[1].x |= ((pkt[4] & 0x3F) << 4);
> > + pt[1].y = ((pkt[5] & 0x80) << 3);
> > + pt[1].y |= ((pkt[5] & 0x3F) << 4);
> > +
> > + if (pkt_id == V7_PACKET_ID_TWO) {
> > + pt[1].x &= ~0x000F;
> > + pt[1].y |= 0x000F;
> > + } else if (pkt_id == V7_PACKET_ID_MULTI) {
> > + pt[1].x &= ~0x003F;
> > + pt[1].y &= ~0x0020;
> > + pt[1].y |= ((pkt[4] & 0x02) << 4);
> > + pt[1].y |= 0x001F;
> > + } else if (pkt_id == V7_PACKET_ID_NEW) {
> > + pt[1].x &= ~0x003F;
> > + pt[1].x |= (pkt[0] & 0x20);
> > + pt[1].y |= 0x000F;
> > + }
> > +
> > + pt[0].y = 0x7FF - pt[0].y;
> > + pt[1].y = 0x7FF - pt[1].y;
> > +
> > + pt[0].z = (pt[0].x && pt[0].y) ? 62 : 0;
> > + pt[1].z = (pt[1].x && pt[1].y) ? 62 : 0;
> > + }
> > +}
> > +
> > +static void alps_decode_packet_v7(struct alps_fields *f,
> > + unsigned char *p,
> > + struct psmouse *psmouse)
> > +{
> > + struct alps_data *priv = psmouse->private;
> > + static struct v7_raw prev_r;
> > +
> > + priv->r.v7.pkt_id = alps_get_packet_id_v7(p);
> > +
> > + alps_get_finger_coordinate_v7(f->pt_img, p,
> priv->r.v7.pkt_id);
> > +
> > + priv->r.v7.rest_left = 0;
> > + priv->r.v7.rest_right = 0;
> > + priv->r.v7.additional_fingers = 0;
> > + priv->phy_btn = 0;
> > +
> > + if (priv->r.v7.pkt_id == V7_PACKET_ID_TWO ||
> > + priv->r.v7.pkt_id == V7_PACKET_ID_MULTI) {
> > + priv->r.v7.rest_left = (p[0] & 0x10) >> 4;
> > + priv->r.v7.rest_right = (p[0] & 0x20) >> 5;
> > + }
> > +
> > + if (priv->r.v7.pkt_id == V7_PACKET_ID_MULTI)
> > + priv->r.v7.additional_fingers = p[5] & 0x03;
> > +
> > + priv->phy_btn = (p[0] & 0x80) >> 7;
> > +
> > + if (priv->r.v7.pkt_id == V7_PACKET_ID_TWO) {
> > + if (f->pt_img[0].z != 0 && f->pt_img[1].z !=
> 0)
> > + priv->r.v7.raw_fn = 2;
> > + else
> > + priv->r.v7.raw_fn = 1;
> > + } else if (priv->r.v7.pkt_id == V7_PACKET_ID_MULTI)
> > + priv->r.v7.raw_fn = 3 +
> priv->r.v7.additional_fingers;
> > + else if (priv->r.v7.pkt_id == V7_PACKET_ID_IDLE)
> > + priv->r.v7.raw_fn = 0;
> > + else if (priv->r.v7.pkt_id == V7_PACKET_ID_NEW)
> > + priv->r.v7.raw_fn = prev_r.raw_fn;
> > +
> > + /* It is a trick to bypass firmware bug of older
> version
> > + that 'New' Packet is missed when finger number
> changed.
> > + We fake a 'New' Packet in such cases.*/
> > + if (priv->r.v7.pkt_id == V7_PACKET_ID_TWO ||
> > + priv->r.v7.pkt_id == V7_PACKET_ID_MULTI ||
> > + priv->r.v7.pkt_id == V7_PACKET_ID_IDLE) {
> > + if (priv->r.v7.raw_fn != prev_r.raw_fn)
> > + priv->r.v7.pkt_id = V7_PACKET_ID_NEW;
> > + }
> > +
> > + memcpy(&prev_r, &priv->r.v7, sizeof(struct v7_raw));
> > +}
> > +
> > +static void alps_set_each_pt_attr_v7(struct psmouse
> *psmouse,
> > + struct alps_abs_data *pt,
> > + struct alps_bl_pt_attr
> *pt_attr)
> > +{
> > + struct alps_data *priv = psmouse->private;
> > + unsigned int dist;
> > +
> > + if (!pt_attr->is_init_pt_got && pt->z != 0) {
> > + pt_attr->is_init_pt_got = 1;
> > + pt_attr->is_counted = 0;
> > + memcpy(&pt_attr->init_pt, pt,
> sizeof(pt_attr->init_pt));
> > + }
> > +
> > + if (pt->z != 0) {
> > + if (pt->y < priv->resting_zone_y_min) {
> > + /* A finger is recognized as a
> non-resting finger
> > + if it's position is outside the
> resting finger zone.*/
> > + pt_attr->zone = ZONE_NORMAL;
> > + pt_attr->is_counted = 1;
> > + } else {
> > + /* A finger is recognized as a resting
> finger if it's
> > + position is inside the resting finger
> zone and there's
> > + no large movement from it's touch down
> position.*/
> > + pt_attr->zone = ZONE_RESTING;
> > +
> > + if (pt->x > priv->x_max / 2)
> > + pt_attr->zone |=
> ZONE_RIGHT_BTN;
> > + else
> > + pt_attr->zone |=
> ZONE_LEFT_BTN;
> > +
> > + /* A resting finger will turn to be a
> non-resting
> > + finger if it has made large movement
> from it's touch
> > + down position. A non-resting finger
> will never turn
> > + to a resting finger before it leaves
> the touchpad
> > + surface.*/
> > + if (pt_attr->is_init_pt_got) {
> > + dist = alps_pt_distance(pt,
> &pt_attr->init_pt);
> > +
> > + if (dist > V7_LARGE_MOVEMENT)
> > + pt_attr->is_counted =
> 1;
> > + }
> > + }
> > + }
> > +}
> > +
> > +static void alps_set_pt_attr_v7(struct psmouse *psmouse,
> > + struct alps_fields *f)
> > +{
> > + struct alps_data *priv = psmouse->private;
> > + int i;
> > +
> > + switch (priv->r.v7.pkt_id) {
> > + case V7_PACKET_ID_TWO:
> > + case V7_PACKET_ID_MULTI:
> > + for (i = 0; i < V7_IMG_PT_NUM; i++) {
> > + alps_set_each_pt_attr_v7(psmouse,
> > +
> &f->pt_img[i],
> > +
> &priv->pt_attr[i]);
> > + }
> > + break;
> > + default:
> > + /*All finger attributes are cleared when
> packet ID is
> > + 'IDLE', 'New'or other unknown IDs. An 'IDLE'
> packet
> > + indicates that there's no finger and no button
> activity.
> > + A 'NEW' packet indicates the finger position
> in packet
> > + is not continues from previous packet. Such as
> the
> > + condition there's finger placed or lifted. In
> these cases,
> > + finger attributes will be reset.*/
> > + memset(priv->pt_attr, 0,
> sizeof(priv->pt_attr[0]) * 2);
> > + break;
> > + }
> > +}
> > +
> > +static void alps_cal_output_finger_num_v7(struct psmouse
> *psmouse,
> > + struct alps_fields *f)
> > +{
> > + struct alps_data *priv = psmouse->private;
> > + unsigned int fn = 0;
> > + int i;
> > +
> > + switch (priv->r.v7.pkt_id) {
> > + case V7_PACKET_ID_IDLE:
> > + case V7_PACKET_ID_NEW:
> > + /*No finger is reported when packet ID is
> 'IDLE' or 'New'.
> > + An 'IDLE' packet indicates that there's no
> finger on touchpad.
> > + A 'NEW' packet indicates there's finger placed
> or lifted.
> > + Finger position of 'New' packet is not
> continues from the
> > + previous packet.*/
> > + fn = 0;
> > + break;
> > + case V7_PACKET_ID_TWO:
> > + if (f->pt_img[0].z == 0) {
> > + /*The first finger slot is zero when a
> non-resting
> > + finger lifted and remaining only one
> resting finger
> > + on touchpad. Hardware report the
> remaining resting
> > + finger in second slot. This resting is
> ignored*/
> > + fn = 0;
> > + } else if (f->pt_img[1].z == 0) {
> > + /* The second finger slot is zero if
> there's
> > + only one finger*/
> > + fn = 1;
> > + } else {
> > + /*All non-resting fingers will be
> counted to report*/
> > + fn = 0;
> > + for (i = 0; i < V7_IMG_PT_NUM; i++) {
> > + if
> (priv->pt_attr[i].is_counted)
> > + fn++;
> > + }
> > +
> > + /*In the case that both fingers are
> > + resting fingers, report the first
> one*/
> > + if (!priv->pt_attr[0].is_counted &&
> > + !priv->pt_attr[1].is_counted) {
> > + fn = 1;
> > + }
> > + }
> > + break;
> > + case V7_PACKET_ID_MULTI:
> > + /*A packet ID 'MULTI' indicats that at least 3
> non-resting
> > + finger exist.*/
> > + fn = 3 + priv->r.v7.additional_fingers;
> > + break;
> > + }
> > +
> > + f->fingers = fn;
> > +}
> > +
> > +static void alps_button_dead_zone_filter(struct psmouse
> *psmouse,
> > + struct alps_fields *f,
> > + struct alps_fields *prev_f)
> > +{
> > + struct alps_data *priv = psmouse->private;
> > + int dx, dy;
> > +
> > + if (priv->prev_phy_btn == 0 && priv->phy_btn != 0) {
> > + memcpy(&priv->pt_attr[0].init_dead_pt,
> > + &f->pt_img[0],
> > + sizeof(struct alps_abs_data));
> > + }
> > +
> > + if (priv->pt_attr[0].init_dead_pt.x != 0 &&
> > + priv->pt_attr[0].init_dead_pt.x != 0) {
> > + dx = f->pt_img[0].x -
> priv->pt_attr[0].init_dead_pt.x;
> > + dy = f->pt_img[0].y -
> priv->pt_attr[0].init_dead_pt.y;
> > + if ((abs(dx) > V7_DEAD_ZONE_OFFSET_X) ||
> > + (abs(dy) > V7_DEAD_ZONE_OFFSET_Y)) {
> > +
> memset(&priv->pt_attr[0].init_dead_pt, 0,
> > + sizeof(struct
> alps_abs_data));
> > + priv->btn_delay_cnt = 0;
> > + } else {
> > + memcpy(&f->pt_img[0],
> > + &prev_f->pt_img[0],
> > + sizeof(struct
> alps_abs_data));
> > + if (priv->prev_phy_btn == 0 &&
> priv->phy_btn != 0)
> > + priv->btn_delay_cnt = 2;
> > + }
> > + }
> > +
> > + if (priv->btn_delay_cnt > 0) {
> > + f->btn.left = 0;
> > + f->btn.right = 0;
> > + priv->btn_delay_cnt--;
> > + }
> > +}
> > +
> > +static void alps_assign_buttons_v7(struct psmouse *psmouse,
> > + struct alps_fields *f,
> > + struct alps_fields *prev_f)
> > +{
> > + struct alps_data *priv = psmouse->private;
> > +
> > + if (priv->phy_btn) {
> > + if (!priv->prev_phy_btn) {
> > + /* Report a right click as long as
> there's finger on
> > + right button zone. Othrewise, report a
> left click.*/
> > + if (priv->r.v7.rest_right ||
> > + priv->pt_attr[0].zone &
> ZONE_RIGHT_BTN ||
> > + priv->pt_attr[1].zone &
> ZONE_RIGHT_BTN) {
> > + f->btn.right = 1;
> > + priv->pressed_btn_bits |=
> RIGHT_BUTTON_BIT;
> > + } else {
> > + f->btn.left = 1;
> > + priv->pressed_btn_bits |=
> LEFT_BUTTON_BIT;
> > + }
> > + } else {
> > + if (priv->pressed_btn_bits &
> RIGHT_BUTTON_BIT)
> > + f->btn.right = 1;
> > + if (priv->pressed_btn_bits &
> LEFT_BUTTON_BIT)
> > + f->btn.left = 1;
> > + }
> > + } else {
> > + priv->pressed_btn_bits = 0;
> > + f->btn.right = 0;
> > + f->btn.left = 0;
> > + }
> > +
> > + alps_button_dead_zone_filter(psmouse, f, prev_f);
> > +
> > + priv->prev_phy_btn = priv->phy_btn;
> > +}
> > +
> > +static void alps_process_packet_v7(struct psmouse *psmouse)
> > +{
> > + struct alps_data *priv = psmouse->private;
> > + struct alps_fields f = {0};
> > + static struct alps_fields prev_f;
> > + unsigned char *packet = psmouse->packet;
> > +
> > + priv->decode_fields(&f, packet, psmouse);
> > +
> > + if (alps_drop_unsupported_packet_v7(psmouse))
> > + return;
> > +
> > + alps_set_pt_attr_v7(psmouse, &f);
> > +
> > + alps_cal_output_finger_num_v7(psmouse, &f);
> > +
> > + alps_assign_buttons_v7(psmouse, &f, &prev_f);
> > +
> > + alps_report_coord_and_btn(psmouse, &f);
> > +
> > + memcpy(&prev_f, &f, sizeof(struct alps_fields));
> > +}
> > +
> > static void alps_report_bare_ps2_packet(struct psmouse
> *psmouse,
> > unsigned char
> packet[],
> > bool report_buttons)
> > @@ -1080,6 +1492,14 @@ static psmouse_ret_t
> alps_process_byte(struct psmouse *psmouse)
> > return PSMOUSE_BAD_DATA;
> > }
> >
> > + if ((priv->proto_version == ALPS_PROTO_V7 &&
> > + !alps_is_valid_package_v7(psmouse))) {
> > + psmouse_dbg(psmouse, "refusing packet[%i] = %x
> \n",
> > + psmouse->pktcnt - 1,
> > + psmouse->packet[psmouse->pktcnt -
> 1]);
> > + return PSMOUSE_BAD_DATA;
> > + }
> > +
> > if (psmouse->pktcnt == psmouse->pktsize) {
> > priv->process_packet(psmouse);
> > return PSMOUSE_FULL_PACKET;
> > @@ -1192,6 +1612,22 @@ static int alps_rpt_cmd(struct
> psmouse *psmouse, int init_command,
> > return 0;
> > }
> >
> > +static int alps_check_valid_firmware_id(unsigned char id[])
> > +{
> > + int valid = 1;
> > +
> > + if (id[0] == 0x73)
> > + valid = 1;
> > + else if (id[0] == 0x88) {
> > + if ((id[1] == 0x07) ||
> > + (id[1] == 0x08) ||
> > + ((id[1] & 0xf0) == 0xB0))
> > + valid = 1;
> > + }
> > +
> > + return valid;
> > +}
> > +
> > static int alps_enter_command_mode(struct psmouse *psmouse)
> > {
> > unsigned char param[4];
> > @@ -1201,8 +1637,7 @@ static int
> alps_enter_command_mode(struct psmouse *psmouse)
> > return -1;
> > }
> >
> > - if ((param[0] != 0x88 || (param[1] != 0x07 &&
> param[1] != 0x08)) &&
> > - param[0] != 0x73) {
> > + if (!alps_check_valid_firmware_id(param)) {
> > psmouse_dbg(psmouse,
> > "unknown response while entering
> command mode\n");
> > return -1;
> > @@ -1704,6 +2139,36 @@ error:
> > return ret;
> > }
> >
> > +static int alps_hw_init_v7(struct psmouse *psmouse)
> > +{
> > + struct ps2dev *ps2dev = &psmouse->ps2dev;
> > + int reg_val, ret = -1;
> > +
> > + if (alps_enter_command_mode(psmouse))
> > + goto error;
> > +
> > + reg_val = alps_command_mode_read_reg(psmouse, 0xc2d9);
> > + if (reg_val == -1)
> > + goto error;
> > +
> > + if (alps_command_mode_write_reg(psmouse, 0xc2c9,
> 0x64))
> > + goto error;
> > +
> > + reg_val = alps_command_mode_read_reg(psmouse, 0xc2c4);
> > + if (reg_val == -1)
> > + goto error;
> > +
> > + if (__alps_command_mode_write_reg(psmouse, reg_val |
> 0x02))
> > + goto error;
> > +
> > + alps_exit_command_mode(psmouse);
> > + return ps2_command(ps2dev, NULL, PSMOUSE_CMD_ENABLE);
> > +
> > +error:
> > + alps_exit_command_mode(psmouse);
> > + return ret;
> > +}
> > +
> > /* Must be in command mode when calling this function */
> > static int alps_absolute_mode_v4(struct psmouse *psmouse)
> > {
> > @@ -1875,6 +2340,7 @@ static void alps_set_defaults(struct
> alps_data *priv)
> > priv->set_abs_params = alps_set_abs_params_st;
> > priv->x_max = 1023;
> > priv->y_max = 767;
> > + priv->slot_number = 1;
> > break;
> > case ALPS_PROTO_V3:
> > priv->hw_init = alps_hw_init_v3;
> > @@ -1883,6 +2349,7 @@ static void alps_set_defaults(struct
> alps_data *priv)
> > priv->decode_fields = alps_decode_pinnacle;
> > priv->nibble_commands =
> alps_v3_nibble_commands;
> > priv->addr_command = PSMOUSE_CMD_RESET_WRAP;
> > + priv->slot_number = 2;
> > break;
> > case ALPS_PROTO_V4:
> > priv->hw_init = alps_hw_init_v4;
> > @@ -1890,6 +2357,7 @@ static void alps_set_defaults(struct
> alps_data *priv)
> > priv->set_abs_params = alps_set_abs_params_mt;
> > priv->nibble_commands =
> alps_v4_nibble_commands;
> > priv->addr_command = PSMOUSE_CMD_DISABLE;
> > + priv->slot_number = 2;
> > break;
> > case ALPS_PROTO_V5:
> > priv->hw_init = alps_hw_init_dolphin_v1;
> > @@ -1905,6 +2373,7 @@ static void alps_set_defaults(struct
> alps_data *priv)
> > priv->y_max = 660;
> > priv->x_bits = 23;
> > priv->y_bits = 12;
> > + priv->slot_number = 2;
> > break;
> > case ALPS_PROTO_V6:
> > priv->hw_init = alps_hw_init_v6;
> > @@ -1913,6 +2382,28 @@ static void alps_set_defaults(struct
> alps_data *priv)
> > priv->nibble_commands =
> alps_v6_nibble_commands;
> > priv->x_max = 2047;
> > priv->y_max = 1535;
> > + priv->slot_number = 2;
> > + break;
> > + case ALPS_PROTO_V7:
> > + priv->hw_init = alps_hw_init_v7;
> > + priv->process_packet = alps_process_packet_v7;
> > + priv->decode_fields = alps_decode_packet_v7;
> > + priv->set_abs_params = alps_set_abs_params_mt;
> > + priv->nibble_commands =
> alps_v3_nibble_commands;
> > + priv->addr_command = PSMOUSE_CMD_RESET_WRAP;
> > + priv->x_max = 0xfff;
> > + priv->y_max = 0x7ff;
> > + priv->resting_zone_y_min = 0x654;
> > + priv->byte0 = 0x48;
> > + priv->mask0 = 0x48;
> > + priv->flags = 0;
> > + priv->slot_number = 2;
> > +
> > + priv->phy_btn = 0;
> > + priv->prev_phy_btn = 0;
> > + priv->btn_delay_cnt = 0;
> > + priv->pressed_btn_bits = 0;
> > + memset(priv->pt_attr, 0,
> sizeof(priv->pt_attr[0]) * 2);
> > break;
> > }
> > }
> > @@ -1982,6 +2473,11 @@ static int alps_identify(struct
> psmouse *psmouse, struct alps_data *priv)
> > return -EIO;
> > else
> > return 0;
> > + } else if (ec[0] == 0x88 && (ec[1] & 0xf0) == 0xB0) {
> > + priv->proto_version = ALPS_PROTO_V7;
> > + alps_set_defaults(priv);
> > +
> > + return 0;
> > } else if (ec[0] == 0x88 && ec[1] == 0x08) {
> > priv->proto_version = ALPS_PROTO_V3;
> > alps_set_defaults(priv);
> > @@ -2045,7 +2541,7 @@ static void
> alps_set_abs_params_mt(struct alps_data *priv,
> > struct input_dev *dev1)
> > {
> > set_bit(INPUT_PROP_SEMI_MT, dev1->propbit);
> > - input_mt_init_slots(dev1, 2, 0);
> > + input_mt_init_slots(dev1, priv->slot_number, 0);
> > input_set_abs_params(dev1, ABS_MT_POSITION_X, 0,
> priv->x_max, 0, 0);
> > input_set_abs_params(dev1, ABS_MT_POSITION_Y, 0,
> priv->y_max, 0, 0);
> >
> > diff --git a/drivers/input/mouse/alps.h
> b/drivers/input/mouse/alps.h
> > index 03f88b6..dedbd27 100644
> > --- a/drivers/input/mouse/alps.h
> > +++ b/drivers/input/mouse/alps.h
> > @@ -18,11 +18,36 @@
> > #define ALPS_PROTO_V4 4
> > #define ALPS_PROTO_V5 5
> > #define ALPS_PROTO_V6 6
> > +#define ALPS_PROTO_V7 7
> > +
> > +#define MAX_IMG_PT_NUM 5
> > +#define V7_IMG_PT_NUM 2
> > +
> > +#define ZONE_NORMAL 0x01
> > +#define ZONE_RESTING 0x02
> > +#define ZONE_LEFT_BTN 0x04
> > +#define ZONE_RIGHT_BTN 0x08
> >
> > #define DOLPHIN_COUNT_PER_ELECTRODE 64
> > #define DOLPHIN_PROFILE_XOFFSET 8 /*
> x-electrode offset */
> > #define DOLPHIN_PROFILE_YOFFSET 1 /*
> y-electrode offset */
> >
> > +/*
> > + * enum V7_PACKET_ID - defines the packet type for V7
> > + * V7_PACKET_ID_IDLE: There's no finger and no button
> activity.
> > + * V7_PACKET_ID_TWO: There's one or two non-resting fingers
> on touchpad
> > + * or there's button activities.
> > + * V7_PACKET_ID_MULTI: There are at least three non-resting
> fingers.
> > + * V7_PACKET_ID_NEW: The finger position in slot is not
> continues from
> > + * previous packet.
> > +*/
> > +enum V7_PACKET_ID {
> > + V7_PACKET_ID_IDLE,
> > + V7_PACKET_ID_TWO,
> > + V7_PACKET_ID_MULTI,
> > + V7_PACKET_ID_NEW,
> > +};
> > +
> > /**
> > * struct alps_model_info - touchpad ID table
> > * @signature: E7 response string to match.
> > @@ -66,15 +91,7 @@ struct alps_nibble_commands {
> > };
> >
> > /**
> > - * struct alps_fields - decoded version of the report
> packet
> > - * @x_map: Bitmap of active X positions for MT.
> > - * @y_map: Bitmap of active Y positions for MT.
> > - * @fingers: Number of fingers for MT.
> > - * @x: X position for ST.
> > - * @y: Y position for ST.
> > - * @z: Z position for ST.
> > - * @first_mp: Packet is the first of a multi-packet report.
> > - * @is_mp: Packet is part of a multi-packet report.
> > + * struct alps_btn - decoded version of the button status
> > * @left: Left touchpad button is active.
> > * @right: Right touchpad button is active.
> > * @middle: Middle touchpad button is active.
> > @@ -82,16 +99,7 @@ struct alps_nibble_commands {
> > * @ts_right: Right trackstick button is active.
> > * @ts_middle: Middle trackstick button is active.
> > */
> > -struct alps_fields {
> > - unsigned int x_map;
> > - unsigned int y_map;
> > - unsigned int fingers;
> > - unsigned int x;
> > - unsigned int y;
> > - unsigned int z;
> > - unsigned int first_mp:1;
> > - unsigned int is_mp:1;
> > -
> > +struct alps_btn {
> > unsigned int left:1;
> > unsigned int right:1;
> > unsigned int middle:1;
> > @@ -102,6 +110,73 @@ struct alps_fields {
> > };
> >
> > /**
> > + * struct alps_btn - decoded version of the X Y Z postion
> for ST.
> > + * @x: X position for ST.
> > + * @y: Y position for ST.
> > + * @z: Z position for ST.
> > + */
> > +struct alps_abs_data {
> > + unsigned int x;
> > + unsigned int y;
> > + unsigned int z;
> > +};
> > +
> > +/**
> > + * struct alps_fields - decoded version of the report
> packet
> > + * @fingers: Number of fingers for MT.
> > + * @pt: X Y Z postion for ST.
> > + * @pt: X Y Z postion for image MT.
> > + * @x_map: Bitmap of active X positions for MT.
> > + * @y_map: Bitmap of active Y positions for MT.
> > + * @first_mp: Packet is the first of a multi-packet report.
> > + * @is_mp: Packet is part of a multi-packet report.
> > + * @btn: Button activity status
> > + */
> > +struct alps_fields {
> > + unsigned int fingers;
> > + struct alps_abs_data pt;
> > + struct alps_abs_data pt_img[MAX_IMG_PT_NUM];
> > + unsigned int x_map;
> > + unsigned int y_map;
> > + unsigned int first_mp:1;
> > + unsigned int is_mp:1;
> > + struct alps_btn btn;
> > +};
> > +
> > +/**
> > + * struct v7_raw - data decoded from raw packet for V7.
> > + * @pkt_id: An id that specifies the type of packet.
> > + * @additional_fingers: Number of additional finger that is
> neighter included
> > + * in pt slot nor reflected in rest_left and rest_right
> flag of data packet.
> > + * @rest_left: There are fingers on left resting zone.
> > + * @rest_right: There are fingers on right resting zone.
> > + * @raw_fn: The number of finger on touchpad.
> > + */
> > +struct v7_raw {
> > + unsigned char pkt_id;
> > + unsigned int additional_fingers;
> > + unsigned char rest_left;
> > + unsigned char rest_right;
> > + unsigned char raw_fn;
> > +};
> > +
> > +/**
> > + * struct alps_bl_pt_attr - generic attributes of touch
> points for buttonless device
> > + * @zone: The part of touchpad that the touch point locates
> > + * @is_counted: The touch point is not a resting finger.
> > + * @is_init_pt_got: The touch down point is got.
> > + * @init_pt: The X Y Z position of the touch down point.
> > + * @init_dead_pt: The touch down point of a finger used by
> dead zone process.
> > + */
> > +struct alps_bl_pt_attr {
> > + unsigned char zone;
> > + unsigned char is_counted;
> > + unsigned char is_init_pt_got;
> > + struct alps_abs_data init_pt;
> > + struct alps_abs_data init_dead_pt;
> > +};
> > +
> > +/**
> > * struct alps_data - private data structure for the ALPS
> driver
> > * @dev2: "Relative" device used to report trackstick or
> mouse activity.
> > * @phys: Physical path for the relative device.
> > @@ -116,8 +191,10 @@ struct alps_fields {
> > * @flags: Additional device capabilities (passthrough
> port, trackstick, etc.).
> > * @x_max: Largest possible X position value.
> > * @y_max: Largest possible Y position value.
> > + * @resting_zone_y_min: Smallest Y postion value of the
> bottom resting zone.
> > * @x_bits: Number of X bits in the MT bitmap.
> > * @y_bits: Number of Y bits in the MT bitmap.
> > + * @img_fingers: Number of image fingers.
> > * @hw_init: Protocol-specific hardware init function.
> > * @process_packet: Protocol-specific function to process a
> report packet.
> > * @decode_fields: Protocol-specific function to read
> packet bitfields.
> > @@ -132,6 +209,11 @@ struct alps_fields {
> > * @fingers: Number of fingers from last MT report.
> > * @quirks: Bitmap of ALPS_QUIRK_*.
> > * @timer: Timer for flushing out the final report packet
> in the stream.
> > + * @v7: Data decoded from raw packet for V7
> > + * @phy_btn: Physical button is active.
> > + * @prev_phy_btn: Physical button of previous packet is
> active.
> > + * @pressed_btn_bits: Pressed positon of button zone
> > + * @pt_attr: Generic attributes of touch points for
> buttonless device.
> > */
> > struct alps_data {
> > struct input_dev *dev2;
> > @@ -145,8 +227,10 @@ struct alps_data {
> > unsigned char flags;
> > int x_max;
> > int y_max;
> > + int resting_zone_y_min;
> > int x_bits;
> > int y_bits;
> > + unsigned char slot_number;
> >
> > int (*hw_init)(struct psmouse *psmouse);
> > void (*process_packet)(struct psmouse *psmouse);
> > @@ -161,6 +245,16 @@ struct alps_data {
> > int fingers;
> > u8 quirks;
> > struct timer_list timer;
> > +
> > + /* these are used for buttonless touchpad*/
> > + union {
> > + struct v7_raw v7;
> > + } r;
> > + unsigned char phy_btn;
> > + unsigned char prev_phy_btn;
> > + unsigned char btn_delay_cnt;
> > + unsigned char pressed_btn_bits;
> > + struct alps_bl_pt_attr pt_attr[MAX_IMG_PT_NUM];
> > };
> >
> > #define ALPS_QUIRK_TRACKSTICK_BUTTONS 1 /*
> trakcstick buttons in trackstick packet */
>
>
>
>
--
To unsubscribe from this list: send the line "unsubscribe linux-input" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at http://vger.kernel.org/majordomo-info.html
next prev parent reply other threads:[~2014-04-29 13:52 UTC|newest]
Thread overview: 16+ messages / expand[flat|nested] mbox.gz Atom feed top
2014-03-19 8:55 [PATCH] input: add support for ALPS v7 protocol device Qiting Chen
2014-03-19 9:11 ` vencik
2014-03-26 12:20 ` Václav Krpec
[not found] ` <CAKvfdtKRqaCVJiFQcF2Q+buSRpacLisaMz6eHK9GmXZbvz9bAA@mail.gmail.com>
2014-03-27 8:41 ` vencik
2014-03-27 9:47 ` vencik
2014-03-27 10:10 ` vencik
2014-03-28 3:36 ` Elaine Chen
2014-04-29 13:52 ` Václav Krpec [this message]
2014-04-14 3:05 ` Elaine Chen
2014-04-21 2:12 ` Elaine Chen
2014-04-22 5:26 ` Dmitry Torokhov
2014-04-22 5:42 ` Peter Hutterer
2014-04-22 6:11 ` Elaine Chen
2014-04-22 6:55 ` Peter Hutterer
2014-04-23 6:47 ` Elaine Chen
2014-04-23 19:57 ` Dmitry Torokhov
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=1398779549.4630.90.camel@adora \
--to=vencik@razdva.cz \
--cc=cernekee@gmail.com \
--cc=dmitry.torokhov@gmail.com \
--cc=drejc.kopac@fmf.uni-lj.si \
--cc=dturvene@dahetral.com \
--cc=elaineee66@gmail.com \
--cc=jclift@redhat.com--cc \
--cc=justin@gluster.org \
--cc=linux-input@vger.kernel.org \
--cc=ndevos@redhat.com \
--cc=qiting.chen@cn.alps.com \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).