Hi Samu, On Thu, Nov 12, 2009 at 09:44:26AM +0200, Samu Onkalo wrote: > Provide support for AMI305 / AK8974 magnetometer chips. > coordinates are provided as polled input device. > Selftest and noise filtering (polled input device fuzziness) > are provided via sysfs. > > Signed-off-by: Samu Onkalo <samu.p.onkalo@xxxxxxxxx> > --- > drivers/misc/Kconfig | 11 + > drivers/misc/Makefile | 1 + > drivers/misc/ami305.c | 644 ++++++++++++++++++++++++++++++++++++++++++++ > include/linux/i2c/ami305.h | 22 ++ > 4 files changed, 678 insertions(+), 0 deletions(-) > create mode 100644 drivers/misc/ami305.c > create mode 100644 include/linux/i2c/ami305.h > > diff --git a/drivers/misc/Kconfig b/drivers/misc/Kconfig > index df1f86b..d9d0efc 100644 > --- a/drivers/misc/Kconfig > +++ b/drivers/misc/Kconfig > @@ -246,6 +246,17 @@ config EP93XX_PWM > To compile this driver as a module, choose M here: the module will > be called ep93xx_pwm. > > +config AMI305 > + tristate "AMI305"; > + depends on I2C > + default n > + ---help--- > + Say Y here if you want to build a driver for AMI305 magnetometer > + chip. > + > + To compile this driver as a module, choose M here: the > + module will be called ami305. If unsure, say N here. > + > source "drivers/misc/c2port/Kconfig" > source "drivers/misc/eeprom/Kconfig" > source "drivers/misc/cb710/Kconfig" > diff --git a/drivers/misc/Makefile b/drivers/misc/Makefile > index f982d2e..bb5f969 100644 > --- a/drivers/misc/Makefile > +++ b/drivers/misc/Makefile > @@ -20,6 +20,7 @@ obj-$(CONFIG_SGI_GRU) += sgi-gru/ > obj-$(CONFIG_HP_ILO) += hpilo.o > obj-$(CONFIG_ISL29003) += isl29003.o > obj-$(CONFIG_EP93XX_PWM) += ep93xx_pwm.o > +obj-$(CONFIG_AMI305) += ami305.o > obj-$(CONFIG_C2PORT) += c2port/ > obj-y += eeprom/ > obj-y += cb710/ > diff --git a/drivers/misc/ami305.c b/drivers/misc/ami305.c > new file mode 100644 > index 0000000..ead0190 > --- /dev/null > +++ b/drivers/misc/ami305.c > @@ -0,0 +1,644 @@ > +/* > + * ami305.c is driver for AMI305 (Aichi Steel) and > + * AK8974 (Asahi Kasei EMD Corporation) magnetometer chip > + * > + * Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). > + * > + * Contact: Samu Onkalo <samu.p.onkalo@xxxxxxxxx> > + * > + * 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. > + * > + * 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 program; if not, write to the Free Software > + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA > + * 02110-1301 USA > + * > + */ > +#include <linux/module.h> > +#include <linux/i2c.h> > +#include <linux/interrupt.h> > +#include <linux/mutex.h> > +#include <linux/spinlock.h> > +#include <linux/delay.h> > +#include <linux/i2c/ami305.h> > +#include <linux/input-polldev.h> > + > +#define DRV_NAME "ami305" > + > +/* > + * 16-bit registers are little-endian. LSB is at the address defined below > + * and MSB is at the next higher address. > + */ > +#define AMI305_SELFTEST 0x0C > +#define AMI305_INFO 0x0D > +#define AMI305_WHOAMI 0x0F > +#define AMI305_DATA_X 0x10 > +#define AMI305_DATA_Y 0x12 > +#define AMI305_DATA_Z 0x14 > +#define AMI305_INT_SRC 0x16 > +#define AMI305_STATUS 0x18 > +#define AMI305_INT_CLEAR 0x1A > +#define AMI305_CTRL1 0x1B > +#define AMI305_CTRL2 0x1C > +#define AMI305_CTRL3 0x1D > +#define AMI305_INT_CTRL 0x1E > +#define AMI305_OFFSET_X 0x20 > +#define AMI305_OFFSET_Y 0x22 > +#define AMI305_OFFSET_Z 0x24 > +#define AMI305_INT_THRES 0x26 /* absolute any axis value threshold */ > +#define AMI305_PRESET 0x30 > +#define AMI305_TEMP 0x31 > + > +#define AMI305_SELFTEST_IDLE 0x55 > +#define AMI305_SELFTEST_OK 0xAA > + > +#define AMI305_WHOAMI_VALUE_AMI305 0x47 > +#define AMI305_WHOAMI_VALUE_AK8974 0x48 > + > +#define AMI305_INT_X_HIGH 0x80 /* Axis over +threshold */ > +#define AMI305_INT_Y_HIGH 0x40 > +#define AMI305_INT_Z_HIGH 0x20 > +#define AMI305_INT_X_LOW 0x10 /* Axis below -threshold */ > +#define AMI305_INT_Y_LOW 0x08 > +#define AMI305_INT_Z_LOW 0x04 > +#define AMI305_INT_RANGE 0x02 /* Range overflow (any axis) */ > + > +#define AMI305_STATUS_DRDY 0x40 /* Data ready */ > +#define AMI305_STATUS_OVERRUN 0x20 /* Data overrun */ > +#define AMI305_STATUS_INT 0x10 /* Interrupt occurred */ > + > +#define AMI305_CTRL1_POWER 0x80 /* 0 = standby; 1 = active */ > +#define AMI305_CTRL1_RATE 0x10 /* 0 = 10 Hz; 1 = 20 Hz */ > +#define AMI305_CTRL1_FORCE_EN 0x02 /* 0 = normal; 1 = force */ > +#define AMI305_CTRL1_MODE2 0x01 /* 0 */ > + > +#define AMI305_CTRL2_INT_EN 0x10 /* 1 = enable interrupts */ > +#define AMI305_CTRL2_DRDY_EN 0x08 /* 1 = enable data ready signal */ > +#define AMI305_CTRL2_DRDY_POL 0x04 /* 1 = data ready active high */ > + > +#define AMI305_CTRL3_RESET 0x80 /* Software reset */ > +#define AMI305_CTRL3_FORCE 0x40 /* Start forced measurement */ > +#define AMI305_CTRL3_SELFTEST 0x10 /* Set selftest register */ > + > +#define AMI305_INT_CTRL_XEN 0x80 /* Enable interrupt for this axis */ > +#define AMI305_INT_CTRL_YEN 0x40 > +#define AMI305_INT_CTRL_ZEN 0x20 > +#define AMI305_INT_CTRL_XYZEN 0xE0 > +#define AMI305_INT_CTRL_POL 0x08 /* 0 = active low; 1 = active high */ > +#define AMI305_INT_CTRL_PULSE 0x02 /* 0 = latched; 1 = pulse (50 usec) */ > + > +#define AMI305_MAX_RANGE 2048 > +#define AMI305_THRESHOLD_MAX (AMI305_MAX_RANGE - 1) > + > +#define AMI305_POLL_INTERVAL 100 /* ms */ > +#define AMI305_ACTIVATE_DELAY 1 /* ms */ > +#define AMI305_DEFAULT_FUZZ 3 /* input noise filtering */ > +#define AMI305_MEAS_DELAY 6 /* one measurement in ms */ > +#define AMI305_SELFTEST_DELAY 1 /* ms. Spec says 200us min */ > +#define AMI305_RESET_DELAY 5 /* ms */ > + > +#define AMI305_PWR_ON 1 > +#define AMI305_PWR_OFF 0 > + > +#define AMI305_MAX_TRY 2 > + > +struct ami305_chip { > + struct mutex lock; /* Serialize access to chip */ > + struct mutex users_lock; > + struct i2c_client *client; > + struct input_polled_dev *idev; /* input device */ > + > + int max_range; > + int users; > + int fuzz; > + > + s16 x, y, z; /* Latest measurements */ > + s8 axis_x; > + s8 axis_y; > + s8 axis_z; > +}; > + > +static int ami305_write(struct ami305_chip *chip, u8 reg, u8 data) > +{ > + return i2c_smbus_write_byte_data(chip->client, reg, data); > +} > + > +static int ami305_read(struct ami305_chip *chip, u8 reg) > +{ > + return i2c_smbus_read_byte_data(chip->client, reg); > +} > + > +static int ami305_read_block(struct ami305_chip *chip, u8 reg, > + u8 *data, u8 length) > +{ > + s32 result; > + result = i2c_smbus_read_i2c_block_data(chip->client, > + reg, length, data); > + return result; > +} > + > +static int ami305_power(struct ami305_chip *chip, int poweron) > +{ > + int r, v; > + > + v = poweron ? AMI305_CTRL1_POWER : 0; > + v = v | AMI305_CTRL1_FORCE_EN; > + r = ami305_write(chip, AMI305_CTRL1, v); > + if (r < 0) > + return r; > + > + if (poweron) > + msleep(AMI305_ACTIVATE_DELAY); > + > + return 0; > +} > + > +static int ami305_start_measurement(struct ami305_chip *chip) > +{ > + int ctrl3; > + int ret = 0; > + > + ctrl3 = ami305_read(chip, AMI305_CTRL3); > + if (ctrl3 < 0) > + return ctrl3; > + > + ret = ami305_write(chip, AMI305_CTRL3, ctrl3 | AMI305_CTRL3_FORCE); > + > + return ret; > +} > + > +static int ami305_reset(struct ami305_chip *chip) > +{ > + int r = 0; > + > + /* Power on to get register access */ > + r = ami305_power(chip, AMI305_PWR_ON); > + if (r < 0) > + goto fail; > + > + r = ami305_write(chip, AMI305_CTRL3, AMI305_CTRL3_RESET); > + if (r < 0) > + goto fail; > + > + msleep(AMI305_RESET_DELAY); > +fail: > + return r; > +} > + > +static int ami305_add_users(struct ami305_chip *chip) > +{ > + int r = 0; > + > + mutex_lock(&chip->users_lock); > + > + if (chip->users == 0) { > + r = ami305_power(chip, AMI305_PWR_ON); > + if (r < 0) > + goto fail; > + } > + chip->users++; > +fail: > + mutex_unlock(&chip->users_lock); > + return r; > +} > + > +static int ami305_remove_users(struct ami305_chip *chip) > +{ > + int r = 0; > + > + mutex_lock(&chip->users_lock); > + > + if (chip->users != 0) > + chip->users--; > + > + if (chip->users == 0) { > + r = ami305_power(chip, AMI305_PWR_OFF); > + if (r < 0) > + goto fail; > + } > +fail: > + mutex_unlock(&chip->users_lock); > + return r; > +} > + > +static int ami305_configure(struct ami305_chip *chip) > +{ > + int err; > + > + ami305_reset(chip); > + > + ami305_add_users(chip); > + > + err = ami305_write(chip, AMI305_CTRL2, AMI305_CTRL2_DRDY_EN); > + if (err) > + goto fail; > + > + err = ami305_write(chip, AMI305_CTRL3, 0); > + if (err) > + goto fail; > + > + err = ami305_write(chip, AMI305_INT_CTRL, AMI305_INT_CTRL_POL); > + if (err) > + goto fail; > + > + err = ami305_write(chip, AMI305_PRESET, 0); > + if (err) > + goto fail; > + > +fail: > + ami305_remove_users(chip); > + > + return err; > +} > + > +static int ami305_get_axis(s8 axis, s16 hw_values[3]) > +{ > + if (axis > 0) > + return hw_values[axis - 1]; > + else > + return -hw_values[-axis - 1]; > +} > + > +static int ami305_read_values(struct ami305_chip *chip) > +{ > + s16 hw_values[3]; > + int i; > + > + ami305_start_measurement(chip); > + > + i = AMI305_MAX_TRY; > + do { > + msleep(AMI305_MEAS_DELAY); > + if (ami305_read(chip, AMI305_STATUS) & AMI305_STATUS_DRDY) > + break; > + i--; > + } while (i > 0); > + > + if (i == 0) > + return -ENODEV; > + > + /* X, Y, Z are in conscutive addresses. 2 * 3 bytes */ > + ami305_read_block(chip, AMI305_DATA_X, (u8 *)hw_values, 6); > + > + for (i = 0; i < 3; i++) > + hw_values[i] = le16_to_cpu(hw_values[i]); > + > + chip->x = ami305_get_axis(chip->axis_x, hw_values); > + chip->y = ami305_get_axis(chip->axis_y, hw_values); > + chip->z = ami305_get_axis(chip->axis_z, hw_values); > + > + return 0; > +} > + > +static int ami305_selftest(struct ami305_chip *chip) > +{ > + int r; > + int success = 0; > + > + ami305_add_users(chip); > + > + r = ami305_read(chip, AMI305_SELFTEST); > + if (r != AMI305_SELFTEST_IDLE) > + goto out; > + > + r = ami305_read(chip, AMI305_CTRL3); > + if (r < 0) > + goto out; > + > + r = ami305_write(chip, AMI305_CTRL3, r | AMI305_CTRL3_SELFTEST); > + if (r < 0) > + goto out; > + > + msleep(AMI305_SELFTEST_DELAY); > + > + r = ami305_read(chip, AMI305_SELFTEST); > + if (r != AMI305_SELFTEST_OK) > + goto out; > + > + r = ami305_read(chip, AMI305_SELFTEST); > + if (r == AMI305_SELFTEST_IDLE) > + success = 1; > + > + out: > + ami305_remove_users(chip); > + > + return success; > +} > + > +static void ami305_set_input_params(struct ami305_chip *chip, int fuzz) > +{ > + struct input_dev *input_dev = chip->idev->input; > + int range; > + > + range = chip->max_range; > + input_set_abs_params(input_dev, ABS_X, -range, range, fuzz, 0); > + input_set_abs_params(input_dev, ABS_Y, -range, range, fuzz, 0); > + input_set_abs_params(input_dev, ABS_Z, -range, range, fuzz, 0); > + chip->fuzz = fuzz; > +} > +/* > + * SYSFS interface > + */ > + > +static ssize_t ami305_show_selftest(struct device *dev, > + struct device_attribute *attr, char *buf) > +{ > + struct ami305_chip *chip = dev_get_drvdata(dev); > + char *status; > + > + mutex_lock(&chip->lock); > + status = ami305_selftest(chip) ? "OK" : "FAIL"; > + mutex_unlock(&chip->lock); > + > + return sprintf(buf, "%s\n", status); > +} > + > +static DEVICE_ATTR(selftest, S_IRUGO, ami305_show_selftest, NULL); > + > +static ssize_t ami305_show_active(struct device *dev, > + struct device_attribute *attr, char *buf) > +{ > + struct ami305_chip *chip = dev_get_drvdata(dev); > + int status; > + > + /* Read from the chip to reflect real state of the HW */ > + status = ami305_read(chip, AMI305_CTRL1) & AMI305_CTRL1_POWER; Does not this also need chip->lock to make sure we don't interfere with other I2C operations? > + return sprintf(buf, "%s\n", status ? "ON" : "OFF"); > +} > + > +static DEVICE_ATTR(active, S_IRUGO, ami305_show_active, NULL); > + > +static ssize_t ami305_get_fuzz(struct device *dev, > + struct device_attribute *attr, char *buf) > +{ > + struct ami305_chip *chip = dev_get_drvdata(dev); > + > + return sprintf(buf, "%d\n", chip->fuzz); > +} > + > +static ssize_t ami305_set_fuzz(struct device *dev, > + struct device_attribute *attr, const char *buf, > + size_t count) > +{ > + struct ami305_chip *chip = dev_get_drvdata(dev); > + unsigned long fuzz; > + > + if (strict_strtoul(buf, 0, &fuzz)) > + return -EINVAL; > + > + if (fuzz > AMI305_MAX_RANGE) > + return -EINVAL; > + > + ami305_set_input_params(chip, fuzz); > + > + return count; > +} > + > +static DEVICE_ATTR(fuzz, S_IRUGO | S_IWUSR, ami305_get_fuzz, > + ami305_set_fuzz); fuzz (along with absmin, absmax, etc) can already be fetched and adjusted via EVIOCGABS and EVIOCSABS. > + > +static struct attribute *sysfs_attrs[] = { > + &dev_attr_selftest.attr, > + &dev_attr_active.attr, > + &dev_attr_fuzz.attr, > + NULL > +}; > + > +static struct attribute_group ami305_attribute_group = { > + .attrs = sysfs_attrs > +}; > + > +/* > + * Polled input device interface > + */ > + > +static void ami305_poll(struct input_polled_dev *pidev) > +{ > + struct ami305_chip *chip = pidev->private; > + > + mutex_lock(&chip->lock); > + > + ami305_read_values(chip); > + input_report_abs(pidev->input, ABS_X, chip->x); > + input_report_abs(pidev->input, ABS_Y, chip->y); > + input_report_abs(pidev->input, ABS_Z, chip->z); > + input_sync(pidev->input); > + > + mutex_unlock(&chip->lock); > +} > + > +static void ami305_open(struct input_polled_dev *pidev) > +{ > + struct ami305_chip *chip = pidev->private; > + ami305_add_users(chip); > + ami305_poll(pidev); Why do you want to force poll here? I guess I can adjust polldev core to do the first poll immediately. > +} > + > +static void ami305_close(struct input_polled_dev *pidev) > +{ > + struct ami305_chip *chip = pidev->private; > + ami305_remove_users(chip); > +} > + > +int ami305_inputdev_enable(struct ami305_chip *chip) > +{ > + struct input_dev *input_dev; > + int err; > + > + if (chip->idev) > + return -EINVAL; > + > + chip->idev = input_allocate_polled_device(); > + if (!chip->idev) > + return -ENOMEM; > + > + chip->idev->private = chip; > + chip->idev->poll = ami305_poll; > + chip->idev->close = ami305_close; > + chip->idev->open = ami305_open; > + chip->idev->poll_interval = AMI305_POLL_INTERVAL; > + > + input_dev = chip->idev->input; > + input_dev->name = "AMI305 / AK8974 Magnetometer"; > + input_dev->phys = DRV_NAME "/input0"; > + input_dev->id.bustype = BUS_HOST; > + input_dev->id.vendor = 0; > + input_dev->dev.parent = &chip->client->dev; > + > + set_bit(EV_ABS, input_dev->evbit); > + ami305_set_input_params(chip, AMI305_DEFAULT_FUZZ); > + > + err = input_register_polled_device(chip->idev); > + if (err) { > + input_free_polled_device(chip->idev); > + chip->idev = NULL; > + } > + > + return err; > +} > + > +static int __devinit ami305_probe(struct i2c_client *client, > + const struct i2c_device_id *id) > +{ > + struct ami305_chip *chip; > + struct ami305_platform_data *pdata; > + int err = 0; > + int whoami; > + int x, y, z; > + > + chip = kzalloc(sizeof *chip, GFP_KERNEL); > + if (!chip) > + return -ENOMEM; > + > + i2c_set_clientdata(client, chip); > + chip->client = client; > + > + pdata = client->dev.platform_data; > + > + x = AMI305_DEV_X; > + y = AMI305_DEV_Y; > + z = AMI305_DEV_Z; > + > + if (pdata) { > + /* Remap axes */ > + x = pdata->axis_x ? pdata->axis_x : x; > + y = pdata->axis_y ? pdata->axis_y : y; > + z = pdata->axis_z ? pdata->axis_z : z; > + } > + > + if ((abs(x) > 3) | (abs(y) > 3) | (abs(z) > 3)) { > + dev_err(&client->dev, "Incorrect platform data\n"); > + err = -EINVAL; > + goto fail1; > + } > + > + chip->axis_x = x; > + chip->axis_y = y; > + chip->axis_z = z; > + > + chip->max_range = AMI305_MAX_RANGE; > + > + mutex_init(&chip->lock); > + mutex_init(&chip->users_lock); > + > + whoami = ami305_read(chip, AMI305_WHOAMI); > + if (whoami < 0) { > + dev_err(&client->dev, "device not found\n"); > + err = -ENODEV; > + goto fail1; > + } > + > + if (whoami == AMI305_WHOAMI_VALUE_AMI305) { > + dev_dbg(&client->dev, "device is AMI305, ok\n"); > + } else if (whoami == AMI305_WHOAMI_VALUE_AK8974) { > + dev_dbg(&client->dev, "device is AK8974, ok\n"); > + } else { > + dev_err(&client->dev, "device is neither AMI305 nor AK8974\n"); > + err = -ENODEV; > + goto fail1; > + } > + > + err = ami305_configure(chip); > + if (err) > + goto fail1; > + > + err = ami305_inputdev_enable(chip); > + if (err) { > + dev_err(&client->dev, "Cannot setup input device\n"); > + goto fail1; > + } > + > + err = sysfs_create_group(&chip->client->dev.kobj, > + &ami305_attribute_group); > + if (err) > + dev_err(&client->dev, "Sysfs registration failed\n"); > + > + return err; > +fail1: > + kfree(chip); > + return err; > +} > + > +static int __devexit ami305_remove(struct i2c_client *client) > +{ > + struct ami305_chip *chip = i2c_get_clientdata(client); > + > + sysfs_remove_group(&chip->client->dev.kobj, > + &ami305_attribute_group); > + input_unregister_polled_device(chip->idev); > + input_free_polled_device(chip->idev); > + kfree(chip); > + return 0; > +} > + > +#if CONFIG_PM > +static int ami305_suspend(struct i2c_client *client, pm_message_t mesg) > +{ > + struct ami305_chip *chip = i2c_get_clientdata(client); > + ami305_power(chip, AMI305_PWR_OFF); You probably don't want to power itt on if there are no users. > + return 0; > +} > + > +static int ami305_resume(struct i2c_client *client) > +{ > + struct ami305_chip *chip = i2c_get_clientdata(client); > + ami305_power(chip, AMI305_PWR_ON); > + return 0; > +} > + > +static void ami305_shutdown(struct i2c_client *client) > +{ > + struct ami305_chip *chip = i2c_get_clientdata(client); > + ami305_power(chip, AMI305_PWR_OFF); > +} > + > +#else > +#define ami305_suspend NULL > +#define ami305_shutdown NULL > +#define ami305_resume NULL > +#endif > + > +static const struct i2c_device_id ami305_id[] = { > + {DRV_NAME, 0 }, > + {} > +}; > + > +MODULE_DEVICE_TABLE(i2c, ami305_id); > + > +static struct i2c_driver ami305_driver = { > + .driver = { > + .name = DRV_NAME, > + .owner = THIS_MODULE, > + }, > + .suspend = ami305_suspend, > + .shutdown = ami305_shutdown, > + .resume = ami305_resume, > + .probe = ami305_probe, > + .remove = __devexit_p(ami305_remove), > + .id_table = ami305_id, > +}; > + > +static int __init ami305_init(void) > +{ > + return i2c_add_driver(&ami305_driver); > +} > + > +static void __exit ami305_exit(void) > +{ > + i2c_del_driver(&ami305_driver); > +} > + > +MODULE_DESCRIPTION("AMI305 3-axis magnetometer driver"); > +MODULE_AUTHOR("Samu Onkalo, Nokia Corporation"); > +MODULE_LICENSE("GPL v2"); > +MODULE_ALIAS("i2c:" DRV_NAME); > + > +module_init(ami305_init); > +module_exit(ami305_exit); > diff --git a/include/linux/i2c/ami305.h b/include/linux/i2c/ami305.h > new file mode 100644 > index 0000000..6ca1931 > --- /dev/null > +++ b/include/linux/i2c/ami305.h > @@ -0,0 +1,22 @@ > +/* > + * Configuration for AMI305 (and AK8974) magnetometer driver. > + */ > + > +#ifndef __LINUX_I2C_AMI305_H > +#define __LINUX_I2C_AMI305_H > + > +#define AMI305_NO_MAP 0 > +#define AMI305_DEV_X 1 > +#define AMI305_DEV_Y 2 > +#define AMI305_DEV_Z 3 > +#define AMI305_INV_DEV_X -1 > +#define AMI305_INV_DEV_Y -2 > +#define AMI305_INV_DEV_Z -3 > + > +struct ami305_platform_data { > + s8 axis_x; > + s8 axis_y; > + s8 axis_z; > +}; > + > +#endif /* __LINUX_I2C_AMI305_H */ > -- > 1.5.6.3 > > -- > To unsubscribe from this list: send the line "unsubscribe linux-input" in > the body of a message to majordomo@xxxxxxxxxxxxxxx > More majordomo info at http://vger.kernel.org/majordomo-info.html -- Dmitry -- To unsubscribe from this list: send the line "unsubscribe linux-input" in the body of a message to majordomo@xxxxxxxxxxxxxxx More majordomo info at http://vger.kernel.org/majordomo-info.html