@@ -56,6 +56,7 @@ struct elan_tp_data {
struct input_dev *input;
struct input_dev *tp_input; /* trackpoint input node */
struct regulator *vcc;
+ struct list_head list; /* for list of devices needing input handler */
const struct elan_transport_ops *ops;
@@ -63,6 +64,11 @@ struct elan_tp_data {
struct completion fw_completion;
bool in_fw_update;
+ struct work_struct lid_work;
+ bool lid_switch;
+ int lid_value;
+ bool in_inhibit;
+
struct mutex sysfs_mutex;
unsigned int max_x;
@@ -96,6 +102,9 @@ struct elan_tp_data {
u32 quirks; /* Various quirks */
};
+static struct workqueue_struct *elan_mode_wq;
+static LIST_HEAD(elan_devices_with_lid_handler);
+
static u32 elan_i2c_lookup_quirks(u16 ic_type, u16 product_id)
{
static const struct {
@@ -329,6 +338,74 @@ static int elan_initialize(struct elan_tp_data *data, bool skip_reset)
return error;
}
+static int elan_reactivate(struct elan_tp_data *data)
+{
+ struct device *dev = &data->client->dev;
+ int error;
+
+ error = elan_set_power(data, true);
+ if (error)
+ dev_err(dev, "failed to restore power: %d\n", error);
+
+ error = data->ops->sleep_control(data->client, false);
+ if (error) {
+ dev_err(dev,
+ "failed to wake device up: %d\n", error);
+ return error;
+ }
+
+ return error;
+}
+
+static int elan_inhibit(struct input_dev *input_dev)
+{
+ struct elan_tp_data *data = input_get_drvdata(input_dev);
+ struct i2c_client *client = data->client;
+ int error;
+
+ dev_dbg(&client->dev, "inhibiting\n");
+ /*
+ * We are taking the mutex to make sure sysfs operations are
+ * complete before we attempt to bring the device into low[er]
+ * power mode.
+ */
+ error = mutex_lock_interruptible(&data->sysfs_mutex);
+ if (error)
+ return error;
+
+ disable_irq(client->irq);
+
+ error = elan_set_power(data, false);
+ if (error)
+ enable_irq(client->irq);
+
+ data->in_inhibit = true;
+ mutex_unlock(&data->sysfs_mutex);
+
+ return error;
+}
+
+static int elan_uninhibit(struct input_dev *input_dev)
+{
+ struct elan_tp_data *data = input_get_drvdata(input_dev);
+ struct i2c_client *client = data->client;
+ int error;
+
+ dev_dbg(&client->dev, "uninhibiting\n");
+ error = mutex_lock_interruptible(&data->sysfs_mutex);
+ if (error)
+ return error;
+
+ error = elan_reactivate(data);
+ if (error == 0)
+ enable_irq(client->irq);
+
+ data->in_inhibit = false;
+ mutex_unlock(&data->sysfs_mutex);
+
+ return error;
+}
+
static int elan_query_device_info(struct elan_tp_data *data)
{
int error;
@@ -1187,6 +1264,124 @@ static void elan_disable_regulator(void *_data)
regulator_disable(data->vcc);
}
+static void lid_work_handler(struct work_struct *work)
+{
+ struct elan_tp_data *data = container_of(work, struct elan_tp_data,
+ lid_work);
+
+ if (data->lid_value)
+ elan_inhibit(data->input);
+ else
+ elan_uninhibit(data->input);
+
+}
+
+static void elan_input_lid_event(struct input_handle *handle, unsigned int type,
+ unsigned int code, int value)
+{
+ struct elan_tp_data *data, *n;
+
+ if (type == EV_SW && code == SW_LID) {
+ list_for_each_entry_safe(data, n, &elan_devices_with_lid_handler, list) {
+ data->lid_value = value;
+ queue_work(elan_mode_wq, &data->lid_work);
+ }
+ }
+
+}
+
+struct elan_input_lid {
+ struct input_handle handle;
+};
+
+static int elan_input_lid_connect(struct input_handler *handler,
+ struct input_dev *dev,
+ const struct input_device_id *id)
+{
+ struct elan_input_lid *lid;
+ char *name;
+ int error;
+
+ lid = kzalloc(sizeof(*lid), GFP_KERNEL);
+ if (!lid)
+ return -ENOMEM;
+ name = kasprintf(GFP_KERNEL, "elan-i2c-lid-%s", dev_name(&dev->dev));
+ if (!name) {
+ error = -ENOMEM;
+ goto err_free_lid;
+ }
+ lid->handle.dev = dev;
+ lid->handle.handler = handler;
+ lid->handle.name = name;
+ lid->handle.private = lid;
+ error = input_register_handle(&lid->handle);
+ if (error)
+ goto err_free_name;
+ error = input_open_device(&lid->handle);
+ if (error)
+ goto err_unregister_handle;
+ return 0;
+err_unregister_handle:
+ input_unregister_handle(&lid->handle);
+err_free_name:
+ kfree(name);
+err_free_lid:
+ kfree(lid);
+ return error;
+}
+
+static void elan_input_lid_disconnect(struct input_handle *handle)
+{
+ struct elan_input_lid *lid = handle->private;
+
+ input_close_device(handle);
+ input_unregister_handle(handle);
+ kfree(handle->name);
+ kfree(lid);
+}
+
+static const struct input_device_id elan_input_lid_ids[] = {
+ {
+ .flags = INPUT_DEVICE_ID_MATCH_EVBIT | INPUT_DEVICE_ID_MATCH_SWBIT,
+ .evbit = { BIT_MASK(EV_SW) },
+ .swbit = { [BIT_WORD(SW_LID)] = BIT_MASK(SW_LID) },
+ },
+ { },
+};
+
+static struct input_handler elan_input_lid_handler = {
+ .event = elan_input_lid_event,
+ .connect = elan_input_lid_connect,
+ .disconnect = elan_input_lid_disconnect,
+ .name = "elan-i2c-lid",
+ .id_table = elan_input_lid_ids,
+};
+
+static int elan_create_lid_handler(struct elan_tp_data *data)
+{
+ int error = 0;
+
+ elan_mode_wq = create_singlethread_workqueue("elan-i2c-lid");
+ if (elan_mode_wq == NULL)
+ return -ENOMEM;
+ error = input_register_handler(&elan_input_lid_handler);
+ if (error)
+ goto remove_wq;
+
+ data->lid_switch = true;
+ INIT_LIST_HEAD(&data->list);
+ INIT_WORK(&data->lid_work, lid_work_handler);
+ list_add_tail(&data->list, &elan_devices_with_lid_handler);
+
+ return 0;
+
+remove_wq:
+ data->lid_switch = false;
+ destroy_workqueue(elan_mode_wq);
+ elan_mode_wq = NULL;
+ return error;
+}
+
static int elan_probe(struct i2c_client *client)
{
const struct elan_transport_ops *transport_ops;
@@ -1325,6 +1520,10 @@ static int elan_probe(struct i2c_client *client)
}
}
+ error = elan_create_lid_handler(data);
+ if (error)
+ dev_err(dev, "failed to create lid handler: %d\n", error);
+
return 0;
}
@@ -1334,6 +1533,10 @@ static int elan_suspend(struct device *dev)
struct elan_tp_data *data = i2c_get_clientdata(client);
int ret;
+ /* Wait for switch on completion */
+ if (data->lid_switch)
+ flush_workqueue(elan_mode_wq);
+
/*
* We are taking the mutex to make sure sysfs operations are
* complete before we attempt to bring the device into low[er]
@@ -1371,6 +1574,10 @@ static int elan_resume(struct device *dev)
struct elan_tp_data *data = i2c_get_clientdata(client);
int error;
+ /* Wait for switch on completion */
+ if (data->lid_switch)
+ flush_workqueue(elan_mode_wq);
+
if (!device_may_wakeup(dev)) {
error = regulator_enable(data->vcc);
if (error) {