* axial sliders presented by the device.
  */
 
+#include <linux/bits.h>
 #include <linux/completion.h>
 #include <linux/delay.h>
 #include <linux/device.h>
 
 #define IQS269_VER_INFO                                0x00
 #define IQS269_VER_INFO_PROD_NUM               0x4F
+#define IQS269_VER_INFO_FW_NUM_2               0x03
+#define IQS269_VER_INFO_FW_NUM_3               0x10
 
 #define IQS269_SYS_FLAGS                       0x02
 #define IQS269_SYS_FLAGS_SHOW_RESET            BIT(15)
 #define IQS269_SYS_SETTINGS_ULP_UPDATE_MASK    GENMASK(10, 8)
 #define IQS269_SYS_SETTINGS_ULP_UPDATE_SHIFT   8
 #define IQS269_SYS_SETTINGS_ULP_UPDATE_MAX     7
+#define IQS269_SYS_SETTINGS_SLIDER_SWIPE       BIT(7)
 #define IQS269_SYS_SETTINGS_RESEED_OFFSET      BIT(6)
 #define IQS269_SYS_SETTINGS_EVENT_MODE         BIT(5)
 #define IQS269_SYS_SETTINGS_EVENT_MODE_LP      BIT(4)
 #define IQS269_FILT_STR_MAX                    3
 
 #define IQS269_EVENT_MASK_SYS                  BIT(6)
+#define IQS269_EVENT_MASK_GESTURE              BIT(3)
 #define IQS269_EVENT_MASK_DEEP                 BIT(2)
 #define IQS269_EVENT_MASK_TOUCH                        BIT(1)
 #define IQS269_EVENT_MASK_PROX                 BIT(0)
 #define IQS269_MISC_B_TRACKING_UI_ENABLE       BIT(4)
 #define IQS269_MISC_B_FILT_STR_SLIDER          GENMASK(1, 0)
 
+#define IQS269_TIMEOUT_TAP_MS_MAX              4080
+#define IQS269_TIMEOUT_SWIPE_MS_MAX            4080
+#define IQS269_THRESH_SWIPE_MAX                        255
+
 #define IQS269_CHx_ENG_A_MEAS_CAP_SIZE         BIT(15)
 #define IQS269_CHx_ENG_A_RX_GND_INACTIVE       BIT(13)
 #define IQS269_CHx_ENG_A_LOCAL_CAP_SIZE                BIT(12)
        IQS269_EVENT_DEEP_UP,
 };
 
+enum iqs269_slider_id {
+       IQS269_SLIDER_NONE,
+       IQS269_SLIDER_KEY,
+       IQS269_SLIDER_RAW,
+};
+
+enum iqs269_gesture_id {
+       IQS269_GESTURE_TAP,
+       IQS269_GESTURE_HOLD,
+       IQS269_GESTURE_FLICK_POS,
+       IQS269_GESTURE_FLICK_NEG,
+       IQS269_NUM_GESTURES,
+};
+
 struct iqs269_switch_desc {
        unsigned int code;
        bool enabled;
        u8 prod_num;
        u8 sw_num;
        u8 hw_num;
-       u8 padding;
+       u8 fw_num;
 } __packed;
 
 struct iqs269_ch_reg {
        struct regmap *regmap;
        struct mutex lock;
        struct iqs269_switch_desc switches[ARRAY_SIZE(iqs269_events)];
+       struct iqs269_ver_info ver_info;
        struct iqs269_sys_reg sys_reg;
        struct completion ati_done;
        struct input_dev *keypad;
        struct input_dev *slider[IQS269_NUM_SL];
        unsigned int keycode[ARRAY_SIZE(iqs269_events) * IQS269_NUM_CH];
+       unsigned int sl_code[IQS269_NUM_SL][IQS269_NUM_GESTURES];
        unsigned int ch_num;
        bool hall_enable;
        bool ati_current;
 };
 
+static enum iqs269_slider_id iqs269_slider_type(struct iqs269_private *iqs269,
+                                               int slider_num)
+{
+       int i;
+
+       if (!iqs269->sys_reg.slider_select[slider_num])
+               return IQS269_SLIDER_NONE;
+
+       for (i = 0; i < IQS269_NUM_GESTURES; i++)
+               if (iqs269->sl_code[slider_num][i] != KEY_RESERVED)
+                       return IQS269_SLIDER_KEY;
+
+       return IQS269_SLIDER_RAW;
+}
+
 static int iqs269_ati_mode_set(struct iqs269_private *iqs269,
                               unsigned int ch_num, unsigned int mode)
 {
                general |= (val << IQS269_SYS_SETTINGS_ULP_UPDATE_SHIFT);
        }
 
+       if (device_property_present(&client->dev, "linux,keycodes")) {
+               int scale = 1;
+               int count = device_property_count_u32(&client->dev,
+                                                     "linux,keycodes");
+               if (count > IQS269_NUM_GESTURES * IQS269_NUM_SL) {
+                       dev_err(&client->dev, "Too many keycodes present\n");
+                       return -EINVAL;
+               } else if (count < 0) {
+                       dev_err(&client->dev, "Failed to count keycodes: %d\n",
+                               count);
+                       return count;
+               }
+
+               error = device_property_read_u32_array(&client->dev,
+                                                      "linux,keycodes",
+                                                      *iqs269->sl_code, count);
+               if (error) {
+                       dev_err(&client->dev, "Failed to read keycodes: %d\n",
+                               error);
+                       return error;
+               }
+
+               if (device_property_present(&client->dev,
+                                           "azoteq,gesture-swipe"))
+                       general |= IQS269_SYS_SETTINGS_SLIDER_SWIPE;
+
+               /*
+                * Early revisions of silicon use a more granular step size for
+                * tap and swipe gesture timeouts; scale them appropriately.
+                */
+               if (iqs269->ver_info.fw_num < IQS269_VER_INFO_FW_NUM_3)
+                       scale = 4;
+
+               if (!device_property_read_u32(&client->dev,
+                                             "azoteq,timeout-tap-ms", &val)) {
+                       if (val > IQS269_TIMEOUT_TAP_MS_MAX / scale) {
+                               dev_err(&client->dev, "Invalid timeout: %u\n",
+                                       val);
+                               return -EINVAL;
+                       }
+
+                       sys_reg->timeout_tap = val / (16 / scale);
+               }
+
+               if (!device_property_read_u32(&client->dev,
+                                             "azoteq,timeout-swipe-ms",
+                                             &val)) {
+                       if (val > IQS269_TIMEOUT_SWIPE_MS_MAX / scale) {
+                               dev_err(&client->dev, "Invalid timeout: %u\n",
+                                       val);
+                               return -EINVAL;
+                       }
+
+                       sys_reg->timeout_swipe = val / (16 / scale);
+               }
+
+               if (!device_property_read_u32(&client->dev,
+                                             "azoteq,thresh-swipe", &val)) {
+                       if (val > IQS269_THRESH_SWIPE_MAX) {
+                               dev_err(&client->dev, "Invalid threshold: %u\n",
+                                       val);
+                               return -EINVAL;
+                       }
+
+                       sys_reg->thresh_swipe = val;
+               }
+
+               sys_reg->event_mask &= ~IQS269_EVENT_MASK_GESTURE;
+       }
+
        general &= ~IQS269_SYS_SETTINGS_RESEED_OFFSET;
        if (device_property_present(&client->dev, "azoteq,reseed-offset"))
                general |= IQS269_SYS_SETTINGS_RESEED_OFFSET;
 
        /*
         * As per the datasheet, enable streaming during normal-power mode if
-        * either slider is in use. In that case, the device returns to event
-        * mode during low-power mode.
+        * raw coordinates will be read from either slider. In that case, the
+        * device returns to event mode during low-power mode.
         */
-       if (sys_reg->slider_select[0] || sys_reg->slider_select[1])
+       if (iqs269_slider_type(iqs269, 0) == IQS269_SLIDER_RAW ||
+           iqs269_slider_type(iqs269, 1) == IQS269_SLIDER_RAW)
                general |= IQS269_SYS_SETTINGS_EVENT_MODE_LP;
 
        general |= IQS269_SYS_SETTINGS_REDO_ATI;
        }
 
        for (i = 0; i < IQS269_NUM_SL; i++) {
-               if (!iqs269->sys_reg.slider_select[i])
+               if (iqs269_slider_type(iqs269, i) == IQS269_SLIDER_NONE)
                        continue;
 
                iqs269->slider[i] = devm_input_allocate_device(&client->dev);
                if (!iqs269->slider[i])
                        return -ENOMEM;
 
+               iqs269->slider[i]->keycodemax = ARRAY_SIZE(iqs269->sl_code[i]);
+               iqs269->slider[i]->keycode = iqs269->sl_code[i];
+               iqs269->slider[i]->keycodesize = sizeof(**iqs269->sl_code);
+
                iqs269->slider[i]->name = i ? "iqs269a_slider_1"
                                            : "iqs269a_slider_0";
                iqs269->slider[i]->id.bustype = BUS_I2C;
 
-               input_set_capability(iqs269->slider[i], EV_KEY, BTN_TOUCH);
-               input_set_abs_params(iqs269->slider[i], ABS_X, 0, 255, 0, 0);
+               for (j = 0; j < IQS269_NUM_GESTURES; j++)
+                       if (iqs269->sl_code[i][j] != KEY_RESERVED)
+                               input_set_capability(iqs269->slider[i], EV_KEY,
+                                                    iqs269->sl_code[i][j]);
+
+               /*
+                * Present the slider as a narrow trackpad if one or more chan-
+                * nels have been selected to participate, but no gestures have
+                * been mapped to a keycode.
+                */
+               if (iqs269_slider_type(iqs269, i) == IQS269_SLIDER_RAW) {
+                       input_set_capability(iqs269->slider[i],
+                                            EV_KEY, BTN_TOUCH);
+                       input_set_abs_params(iqs269->slider[i],
+                                            ABS_X, 0, 255, 0, 0);
+               }
 
                error = input_register_device(iqs269->slider[i]);
                if (error) {
        if (be16_to_cpu(flags.system) & IQS269_SYS_FLAGS_IN_ATI)
                return 0;
 
-       error = regmap_raw_read(iqs269->regmap, IQS269_SLIDER_X, slider_x,
-                               sizeof(slider_x));
-       if (error) {
-               dev_err(&client->dev, "Failed to read slider position: %d\n",
-                       error);
-               return error;
+       if (iqs269_slider_type(iqs269, 0) == IQS269_SLIDER_RAW ||
+           iqs269_slider_type(iqs269, 1) == IQS269_SLIDER_RAW) {
+               error = regmap_raw_read(iqs269->regmap, IQS269_SLIDER_X,
+                                       slider_x, sizeof(slider_x));
+               if (error) {
+                       dev_err(&client->dev,
+                               "Failed to read slider position: %d\n", error);
+                       return error;
+               }
        }
 
        for (i = 0; i < IQS269_NUM_SL; i++) {
-               if (!iqs269->sys_reg.slider_select[i])
+               flags.gesture >>= (i * IQS269_NUM_GESTURES);
+
+               switch (iqs269_slider_type(iqs269, i)) {
+               case IQS269_SLIDER_NONE:
                        continue;
 
-               /*
-                * Report BTN_TOUCH if any channel that participates in the
-                * slider is in a state of touch.
-                */
-               if (flags.states[IQS269_ST_OFFS_TOUCH] &
-                   iqs269->sys_reg.slider_select[i]) {
-                       input_report_key(iqs269->slider[i], BTN_TOUCH, 1);
-                       input_report_abs(iqs269->slider[i], ABS_X, slider_x[i]);
-               } else {
-                       input_report_key(iqs269->slider[i], BTN_TOUCH, 0);
+               case IQS269_SLIDER_KEY:
+                       for (j = 0; j < IQS269_NUM_GESTURES; j++)
+                               input_report_key(iqs269->slider[i],
+                                                iqs269->sl_code[i][j],
+                                                flags.gesture & BIT(j));
+
+                       if (!(flags.gesture & (BIT(IQS269_GESTURE_FLICK_NEG) |
+                                              BIT(IQS269_GESTURE_FLICK_POS) |
+                                              BIT(IQS269_GESTURE_TAP))))
+                               break;
+
+                       input_sync(iqs269->slider[i]);
+
+                       /*
+                        * Momentary gestures are followed by a complementary
+                        * release cycle so as to emulate a full keystroke.
+                        */
+                       for (j = 0; j < IQS269_NUM_GESTURES; j++)
+                               if (j != IQS269_GESTURE_HOLD)
+                                       input_report_key(iqs269->slider[i],
+                                                        iqs269->sl_code[i][j],
+                                                        0);
+                       break;
+
+               case IQS269_SLIDER_RAW:
+                       /*
+                        * The slider is considered to be in a state of touch
+                        * if any selected channels are in a state of touch.
+                        */
+                       state = flags.states[IQS269_ST_OFFS_TOUCH];
+                       state &= iqs269->sys_reg.slider_select[i];
+
+                       input_report_key(iqs269->slider[i], BTN_TOUCH, state);
+
+                       if (state)
+                               input_report_abs(iqs269->slider[i],
+                                                ABS_X, slider_x[i]);
+                       break;
                }
 
                input_sync(iqs269->slider[i]);
 
 static int iqs269_probe(struct i2c_client *client)
 {
-       struct iqs269_ver_info ver_info;
        struct iqs269_private *iqs269;
        int error;
 
        mutex_init(&iqs269->lock);
        init_completion(&iqs269->ati_done);
 
-       error = regmap_raw_read(iqs269->regmap, IQS269_VER_INFO, &ver_info,
-                               sizeof(ver_info));
+       error = regmap_raw_read(iqs269->regmap, IQS269_VER_INFO,
+                               &iqs269->ver_info, sizeof(iqs269->ver_info));
        if (error)
                return error;
 
-       if (ver_info.prod_num != IQS269_VER_INFO_PROD_NUM) {
+       if (iqs269->ver_info.prod_num != IQS269_VER_INFO_PROD_NUM) {
                dev_err(&client->dev, "Unrecognized product number: 0x%02X\n",
-                       ver_info.prod_num);
+                       iqs269->ver_info.prod_num);
                return -EINVAL;
        }