Hi Chris, > Many small changes and some larger ones in this version (thanks Jonathan!). > > Note the custom i2c_read funtion is used to ensure block read functionality; > on my test hardware my function works but i2c_smbus_read_block_data does not > for some reason. (note as explained at the end, the following actually ends up agreeing with what you have!) Gah, I think in my original review I got the various incredibly similarly named i2c functions confused. What I meant was... i2c_smbus_read_i2c_block_data. The smbus_read_block data one doesn't require to you to specify the length of data (and hence needs hardware support (hardware must put the length in the first byte). The i2c_smbus_i2c_read_block data requires you to say what is expected and I think 'should' work on any i2c adapter. If you look in drivers/i2c/i2c-core.c It sticks the read length in to data.block[0], before calling i2c_smbus_xfer. If we then take the emulated path, that value is pulled out into msg[1].len. Assuming I've followed it through right: i2c_smbus_read_i2c_block_data(tj9->client, addr , data, len); leads to the following messages. struct i2c_msg msgs[] = { { .addr = tj9->client->addr, .flags = tj9->client->flags, .len = 1, .msgbuf0, // a local buffer with first element being set to addr. }, { .addr = tj9->client->addr, .flags = tj9->client->flags | I2C_M_RD, .len = len, .buf = msgbuf2, //another local bounce buffer. } } After transfer is done, we then have: case I2C_SMBUS_I2C_BLOCK_DATA: for (i = 0; i < data->block[0]; i++) data->block[i+1] = msgbuf1[i]; break; Which rather tediously from your point of view means that the data ends up missaligned by 1 byte. I guess this is to emulate the i2c_smbus_read_block_data functional form. So after all this, I'd leave it as it currently is! Sorry for the wild goose chase. Couple more nitpicks inline... > > I also have an RFC on linux-input regarding the usage of the delay sysfs > attribute. Currently it accepts an integer in milliseconds, and uses this to > set the output data rate of the part and the new poll interval if applicable, > and I want to be sure that this is a reasonable implementation. > > There is also some discussion about the axis_map and negate platform data > variables. Currently the system is set up so that the device manufacturer can > mount the sensor in any orientation within the host device, and still have the > driver output data relative to the device instead of the part itself. The > current implementation also allows the device manufacturer to support their > chosen operating system's API requirements for axis direction (right-hand-rule > vs. left-hand-rule), but at 6 variables it could be a little confusing or > excessive. Subject to responses to RFC on delay control, I'm happy with this driver. Good work and I look forward to lots more drivers in the future. > > Signed-off-by: Chris Hudson <chudson@xxxxxxxxxx> Acked-by: Jonathan Cameron <jic23@xxxxxxxxx> > --- > drivers/input/misc/Kconfig | 10 + > drivers/input/misc/Makefile | 1 + > drivers/input/misc/kxtj9.c | 635 +++++++++++++++++++++++++++++++++++++++++++ > include/linux/input/kxtj9.h | 90 ++++++ > 4 files changed, 736 insertions(+), 0 deletions(-) > create mode 100644 drivers/input/misc/kxtj9.c > create mode 100644 include/linux/input/kxtj9.h > > diff --git a/drivers/input/misc/Kconfig b/drivers/input/misc/Kconfig > index f9cf088..567f3d2 100644 > --- a/drivers/input/misc/Kconfig > +++ b/drivers/input/misc/Kconfig > @@ -467,4 +467,14 @@ config INPUT_XEN_KBDDEV_FRONTEND > To compile this driver as a module, choose M here: the > module will be called xen-kbdfront. > > +config INPUT_KXTJ9 > + tristate "Kionix KXTJ9 tri-axis digital accelerometer" > + depends on I2C && SYSFS > + help > + If you say yes here you get support for the Kionix KXTJ9 digital > + tri-axis accelerometer. > + > + This driver can also be built as a module. If so, the module > + will be called kxtj9. > + > endif > diff --git a/drivers/input/misc/Makefile b/drivers/input/misc/Makefile > index e3f7984..f2477ef 100644 > --- a/drivers/input/misc/Makefile > +++ b/drivers/input/misc/Makefile > @@ -25,6 +25,7 @@ obj-$(CONFIG_INPUT_DM355EVM) += dm355evm_keys.o > obj-$(CONFIG_HP_SDC_RTC) += hp_sdc_rtc.o > obj-$(CONFIG_INPUT_IXP4XX_BEEPER) += ixp4xx-beeper.o > obj-$(CONFIG_INPUT_KEYSPAN_REMOTE) += keyspan_remote.o > +obj-$(CONFIG_INPUT_KXTJ9) += kxtj9.o > obj-$(CONFIG_INPUT_M68K_BEEP) += m68kspkr.o > obj-$(CONFIG_INPUT_MAX8925_ONKEY) += max8925_onkey.o > obj-$(CONFIG_INPUT_PCAP) += pcap_keys.o > diff --git a/drivers/input/misc/kxtj9.c b/drivers/input/misc/kxtj9.c > new file mode 100644 > index 0000000..5f4cd66 > --- /dev/null > +++ b/drivers/input/misc/kxtj9.c > @@ -0,0 +1,635 @@ > +/* > + * Copyright (C) 2011 Kionix, Inc. > + * Written by Chris Hudson <chudson@xxxxxxxxxx> > + * > + * 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., 59 Temple Place, Suite 330, Boston, MA > + * 02111-1307, USA > + */ > + > +#include <linux/delay.h> > +#include <linux/i2c.h> > +#include <linux/input.h> > +#include <linux/interrupt.h> > +#include <linux/slab.h> > +#include <linux/input/kxtj9.h> > + > +#define NAME "kxtj9" > +#define G_MAX 8000 > +/* OUTPUT REGISTERS */ > +#define XOUT_L 0x06 > +#define WHO_AM_I 0x0F > +/* CONTROL REGISTERS */ > +#define INT_REL 0x1A > +#define CTRL_REG1 0x1B > +#define INT_CTRL1 0x1E > +#define DATA_CTRL 0x21 > +/* CONTROL REGISTER 1 BITS */ > +#define PC1_OFF 0x7F > +#define PC1_ON 0x80 > +/* INPUT_ABS CONSTANTS */ > +#define FUZZ 3 > +#define FLAT 3 > +/* RESUME STATE INDICES */ > +#define RES_DATA_CTRL 0 > +#define RES_CTRL_REG1 1 > +#define RES_INT_CTRL1 2 > +#define RESUME_ENTRIES 3 > + > +/* > + * The following table lists the maximum appropriate poll interval for each > + * available output data rate. > + */ > +static const struct { > + unsigned int cutoff; > + u8 mask; > +} kxtj9_odr_table[] = { > + { > + 3, ODR800F}, { > + 5, ODR400F}, { > + 10, ODR200F}, { > + 20, ODR100F}, { > + 40, ODR50F}, { > + 80, ODR25F}, { > + 0, ODR12_5F}, > +}; > + > +struct kxtj9_data { > + struct i2c_client *client; > + struct kxtj9_platform_data pdata; > + struct mutex lock; > + struct delayed_work input_work; > + struct input_dev *input_dev; > + > + bool hw_initialized; > + atomic_t enabled; > + u8 shift; > + u8 resume[RESUME_ENTRIES]; > +}; > + > +static int kxtj9_i2c_read(struct kxtj9_data *tj9, u8 addr, u8 *data, int len) > +{ > + int err; > + > + struct i2c_msg msgs[] = { > + { > + .addr = tj9->client->addr, > + .flags = tj9->client->flags, > + .len = 1, > + .buf = &addr, > + }, > + { > + .addr = tj9->client->addr, > + .flags = tj9->client->flags | I2C_M_RD, > + .len = len, > + .buf = data, > + }, > + }; > + err = i2c_transfer(tj9->client->adapter, msgs, 2); > + > + return err; > +} > + > +static int kxtj9_verify(struct kxtj9_data *tj9) > +{ > + int ret; > + > + ret = i2c_smbus_read_byte_data(tj9->client, WHO_AM_I); > + if (ret < 0) > + dev_err(&tj9->client->dev, "read err int source\n"); > + if (ret != 0x06) Nitpick - there ought to be a more meaningful error code here. -EINVAL perhaps? > + ret = -1; > + > + return ret; > +} > + static (this applies to all the functions). > +int kxtj9_update_g_range(struct kxtj9_data *tj9, u8 new_g_range) > +{ > + switch (new_g_range) { > + case KXTJ9_G_2G: > + tj9->shift = SHIFT_ADJ_2G; > + break; > + case KXTJ9_G_4G: > + tj9->shift = SHIFT_ADJ_4G; > + break; > + case KXTJ9_G_8G: > + tj9->shift = SHIFT_ADJ_8G; > + break; > + default: > + return -EINVAL; > + } > + tj9->resume[RES_CTRL_REG1] = (tj9->resume[RES_CTRL_REG1] & 0xE7) > + | new_g_range; > + > + return 0; > +} > + > +int kxtj9_update_odr(struct kxtj9_data *tj9, int poll_interval) > +{ > + int err; > + int i; > + u8 config; > + u8 temp = 0; > + > + /* Use the lowest ODR that can support the requested poll interval */ > + for (i = 0; i < ARRAY_SIZE(kxtj9_odr_table); i++) { > + config = kxtj9_odr_table[i].mask; > + if (poll_interval < kxtj9_odr_table[i].cutoff) > + break; > + } > + > + if (atomic_read(&tj9->enabled)) { > + err = i2c_smbus_write_byte_data(tj9->client, CTRL_REG1, temp); > + if (err < 0) > + return err; > + err = i2c_smbus_write_byte_data(tj9->client, DATA_CTRL, config); > + if (err < 0) > + return err; > + temp = tj9->resume[RES_CTRL_REG1]; > + err = i2c_smbus_write_byte_data(tj9->client, CTRL_REG1, temp); > + if (err < 0) > + return err; > + /* Valid input_dev indicates that input_init passed and this > + * workqueue is available */ Nitpick - unwanted bracket. I would have thought checkpatch would ahve highlighted that... Please verify it isn't giving you any warnings.. > + if (tj9->input_dev) { > + if (!tj9->pdata.drdy_enable) { > + cancel_delayed_work_sync(&tj9->input_work); > + schedule_delayed_work(&tj9->input_work, > + msecs_to_jiffies(poll_interval)); > + } > + } > + } > + tj9->resume[RES_DATA_CTRL] = config; > + > + return 0; > +} > + > +static int kxtj9_hw_init(struct kxtj9_data *tj9) > +{ > + int err; > + u8 buf = 0; > + > + err = i2c_smbus_write_byte_data(tj9->client, CTRL_REG1, buf); > + if (err < 0) > + return err; > + err = i2c_smbus_write_byte_data(tj9->client, DATA_CTRL, > + tj9->resume[RES_DATA_CTRL]); > + if (err < 0) > + return err; > + err = i2c_smbus_write_byte_data(tj9->client, INT_CTRL1, > + tj9->resume[RES_INT_CTRL1]); > + if (err < 0) > + return err; > + > + err = kxtj9_update_g_range(tj9, tj9->pdata.g_range); > + if (err < 0) > + return err; > + > + buf = (tj9->resume[RES_CTRL_REG1] | PC1_ON); > + err = i2c_smbus_write_byte_data(tj9->client, CTRL_REG1, buf); > + if (err < 0) > + return err; > + tj9->resume[RES_CTRL_REG1] = buf; > + > + err = kxtj9_update_odr(tj9, tj9->pdata.poll_interval); > + if (err < 0) > + return err; > + > + tj9->hw_initialized = true; > + > + return 0; > +} > + > +static void kxtj9_device_power_off(struct kxtj9_data *tj9) > +{ > + int err; > + u8 buf; > + > + buf = tj9->resume[RES_CTRL_REG1] & PC1_OFF; > + err = i2c_smbus_write_byte_data(tj9->client, CTRL_REG1, buf); > + if (err < 0) > + dev_err(&tj9->client->dev, "soft power off failed\n"); > + if (tj9->pdata.power_off) > + tj9->pdata.power_off(); > + tj9->resume[RES_CTRL_REG1] = buf; > + tj9->hw_initialized = false; > +} > + > +static int kxtj9_device_power_on(struct kxtj9_data *tj9) > +{ > + int err; > + > + if (tj9->pdata.power_on) { > + err = tj9->pdata.power_on(); > + if (err < 0) > + return err; > + } > + if (!tj9->hw_initialized) { > + mdelay(40); nitpick - is it alright to sleep for longer? If slow msleep is a better bet as lets the processor get on with something useful in the meantime. > + err = kxtj9_hw_init(tj9); > + if (err < 0) { > + kxtj9_device_power_off(tj9); > + return err; > + } > + } > + > + return 0; > +} > + > +static void kxtj9_report_values(struct kxtj9_data *tj9, s16 *xyz) > +{ > + input_report_abs(tj9->input_dev, ABS_X, (int) xyz[0]); > + input_report_abs(tj9->input_dev, ABS_Y, (int) xyz[1]); > + input_report_abs(tj9->input_dev, ABS_Z, (int) xyz[2]); > +} > + > +static int kxtj9_get_acceleration_data(struct kxtj9_data *tj9, s16 *xyz) > +{ > + int err; > + /* Data bytes from hardware xL, xH, yL, yH, zL, zH */ > + s16 acc_data[3]; > + > + err = kxtj9_i2c_read(tj9, XOUT_L, (u8 *)acc_data, 6); > + if (err < 0) > + return err; > + > + acc_data[0] = le16_to_cpu(acc_data[0]); > + acc_data[1] = le16_to_cpu(acc_data[1]); > + acc_data[2] = le16_to_cpu(acc_data[2]); > + > + acc_data[0] >>= tj9->shift; > + acc_data[1] >>= tj9->shift; > + acc_data[2] >>= tj9->shift; > + > + xyz[0] = (tj9->pdata.negate_x) ? (-acc_data[tj9->pdata.axis_map_x]) > + : (acc_data[tj9->pdata.axis_map_x]); > + xyz[1] = (tj9->pdata.negate_y) ? (-acc_data[tj9->pdata.axis_map_y]) > + : (acc_data[tj9->pdata.axis_map_y]); > + xyz[2] = (tj9->pdata.negate_z) ? (-acc_data[tj9->pdata.axis_map_z]) > + : (acc_data[tj9->pdata.axis_map_z]); > + > + return err; > +} > + > +static irqreturn_t kxtj9_isr(int irq, void *dev) > +{ > + struct kxtj9_data *tj9 = dev; > + int ret; > + s16 xyz[3]; > + > + /* data ready is the only possible interrupt type */ > + if (kxtj9_get_acceleration_data(tj9, xyz) == 0) > + kxtj9_report_values(tj9, xyz); > + > + ret = i2c_smbus_read_byte_data(tj9->client, INT_REL); > + if (ret < 0) > + dev_err(&tj9->client->dev, > + "error clearing interrupt status: %d\n", ret); > + > + return IRQ_HANDLED; > +} > + > +static int kxtj9_enable(struct kxtj9_data *tj9) > +{ > + int err; > + > + if (!atomic_cmpxchg(&tj9->enabled, 0, 1)) { > + err = kxtj9_device_power_on(tj9); > + if (err < 0) { > + dev_err(&tj9->client->dev, > + "error during power-on: %d\n", err); > + atomic_set(&tj9->enabled, 0); > + return err; > + } > + err = i2c_smbus_read_byte_data(tj9->client, INT_REL); > + if (err < 0) { > + dev_err(&tj9->client->dev, > + "error clearing interrupt: %d\n", err); Given this has the same cleanup as the previous error, it might be worth dropping them out to a single exit point via goto. (Just put the next too lines after the return below. > + atomic_set(&tj9->enabled, 0); > + return err; > + } > + if (!tj9->pdata.drdy_enable) > + schedule_delayed_work(&tj9->input_work, > + msecs_to_jiffies(tj9->pdata.poll_interval)); > + } > + > + return 0; > +} > + > +static int kxtj9_disable(struct kxtj9_data *tj9) > +{ > + if (atomic_cmpxchg(&tj9->enabled, 1, 0)) { > + if (!tj9->pdata.drdy_enable) > + cancel_delayed_work_sync(&tj9->input_work); > + kxtj9_device_power_off(tj9); > + } > + > + return 0; > +} > + > +static void kxtj9_input_work_func(struct work_struct *work) > +{ > + struct kxtj9_data *tj9 = container_of((struct delayed_work *)work, > + struct kxtj9_data, input_work); > + s16 xyz[3]; > + mutex_lock(&tj9->lock); > + > + if (kxtj9_get_acceleration_data(tj9, xyz) == 0) > + kxtj9_report_values(tj9, xyz); > + > + schedule_delayed_work(&tj9->input_work, > + msecs_to_jiffies(tj9->pdata.poll_interval)); > + mutex_unlock(&tj9->lock); > +} > + > +int kxtj9_input_open(struct input_dev *input) > +{ > + return kxtj9_enable(input_get_drvdata(input)); > +} > + > +void kxtj9_input_close(struct input_dev *dev) > +{ > + kxtj9_disable(input_get_drvdata(dev)); > +} > + > +static int kxtj9_input_init(struct kxtj9_data *tj9) > +{ > + int err; > + > + INIT_DELAYED_WORK(&tj9->input_work, kxtj9_input_work_func); > + tj9->input_dev = input_allocate_device(); > + if (!tj9->input_dev) { > + err = -ENOMEM; > + dev_err(&tj9->client->dev, "input device allocate failed\n"); > + goto err0; > + } > + tj9->input_dev->open = kxtj9_input_open; > + tj9->input_dev->close = kxtj9_input_close; > + > + input_set_drvdata(tj9->input_dev, tj9); > + > + set_bit(EV_ABS, tj9->input_dev->evbit); > + set_bit(EV_REL, tj9->input_dev->evbit); > + > + input_set_abs_params(tj9->input_dev, ABS_X, -G_MAX, G_MAX, FUZZ, FLAT); > + input_set_abs_params(tj9->input_dev, ABS_Y, -G_MAX, G_MAX, FUZZ, FLAT); > + input_set_abs_params(tj9->input_dev, ABS_Z, -G_MAX, G_MAX, FUZZ, FLAT); > + > + tj9->input_dev->name = "kxtj9_accel"; > + > + err = input_register_device(tj9->input_dev); > + if (err) { > + dev_err(&tj9->client->dev, > + "unable to register input polled device %s: %d\n", > + tj9->input_dev->name, err); > + goto err1; > + } > + > + return 0; > +err1: > + input_free_device(tj9->input_dev); > +err0: > + return err; > +} > + > +static void kxtj9_input_cleanup(struct kxtj9_data *tj9) > +{ > + input_unregister_device(tj9->input_dev); > +} > + > +/* kxtj9_delay_show: returns the currently selected poll interval (in ms) */ > +static ssize_t kxtj9_delay_show(struct device *dev, > + struct device_attribute *attr, char *buf) > +{ > + struct kxtj9_data *tj9 = i2c_get_clientdata(to_i2c_client(dev)); > + return sprintf(buf, "%d\n", tj9->pdata.poll_interval); > +} > + > +/* kxtj9_delay_store: allows the user to select a new poll interval (in ms) */ > +static ssize_t kxtj9_delay_store(struct device *dev, > + struct device_attribute *attr, > + const char *buf, size_t count) > +{ > + struct kxtj9_data *tj9 = i2c_get_clientdata(to_i2c_client(dev)); > + unsigned long val; > + int ret = kstrtoul(buf, 10, &val); > + if (ret < 0) > + return ret; > + > + mutex_lock(&tj9->lock); > + /* the selected interval is the greater of the minimum interval or > + the requested interval */ > + tj9->pdata.poll_interval = max((int)val, tj9->pdata.min_interval); > + kxtj9_update_odr(tj9, tj9->pdata.poll_interval); > + mutex_unlock(&tj9->lock); > + > + return count; > +} > + > +/* kxtj9_enable_show: returns the enable status of the part */ > +static ssize_t kxtj9_enable_show(struct device *dev, > + struct device_attribute *attr, char *buf) > +{ > + struct kxtj9_data *tj9 = i2c_get_clientdata(to_i2c_client(dev)); > + return sprintf(buf, "%d\n", atomic_read(&tj9->enabled)); > +} > + > +/* kxtj9_enable_store: allows the user to enable or disable the part */ > +static ssize_t kxtj9_enable_store(struct device *dev, > + struct device_attribute *attr, > + const char *buf, size_t count) > +{ > + struct kxtj9_data *tj9 = i2c_get_clientdata(to_i2c_client(dev)); > + unsigned long val; > + int ret = kstrtoul(buf, 10, &val); > + if (ret < 0) > + return ret; You could use the just merged strtobool > + > + mutex_lock(&tj9->lock); > + if (val) /* non-zero argument enables the part */ > + kxtj9_enable(tj9); > + else /* zero argument disables the part */ > + kxtj9_disable(tj9); > + mutex_unlock(&tj9->lock); > + > + return count; > +} > + > +static DEVICE_ATTR(delay, S_IRUGO|S_IWUSR, kxtj9_delay_show, kxtj9_delay_store); > +static DEVICE_ATTR(enable, S_IRUGO|S_IWUSR, kxtj9_enable_show, > + kxtj9_enable_store); > + > +static struct attribute *kxtj9_attributes[] = { > + &dev_attr_delay.attr, > + &dev_attr_enable.attr, > + NULL > +}; > + > +static struct attribute_group kxtj9_attribute_group = { > + .attrs = kxtj9_attributes > +}; > + > +static int __devinit kxtj9_probe(struct i2c_client *client, > + const struct i2c_device_id *id) > +{ > + int err = -1; > + struct kxtj9_data *tj9 = kzalloc(sizeof(*tj9), GFP_KERNEL); > + if (tj9 == NULL) { > + dev_err(&client->dev, > + "failed to allocate memory for module data\n"); > + err = -ENOMEM; > + goto err0; > + } > + if (client->dev.platform_data == NULL) { > + dev_err(&client->dev, "platform data is NULL; exiting\n"); > + err = -ENODEV; > + goto err0; > + } > + if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C | > + I2C_FUNC_SMBUS_BYTE_DATA)) { > + dev_err(&client->dev, "client not i2c capable\n"); > + err = -ENODEV; > + goto err0; > + } > + mutex_init(&tj9->lock); > + mutex_lock(&tj9->lock); > + tj9->client = client; > + i2c_set_clientdata(client, tj9); > + > + memcpy(&tj9->pdata, client->dev.platform_data, sizeof(tj9->pdata)); > + if (tj9->pdata.init) { > + err = tj9->pdata.init(); > + if (err < 0) > + goto err1; > + } > + > + err = sysfs_create_group(&client->dev.kobj, &kxtj9_attribute_group); > + if (err) > + goto err2; > + > + tj9->resume[RES_CTRL_REG1] = tj9->pdata.res_12bit | tj9->pdata.g_range | > + tj9->pdata.drdy_enable; > + tj9->resume[RES_INT_CTRL1] = tj9->pdata.int_response | > + tj9->pdata.int_polarity | > + tj9->pdata.int_enable; > + tj9->resume[RES_DATA_CTRL] = tj9->pdata.data_odr_init; > + > + err = kxtj9_device_power_on(tj9); > + if (err < 0) > + goto err3; > + atomic_set(&tj9->enabled, 1); > + > + err = kxtj9_verify(tj9); > + if (err < 0) { > + dev_err(&client->dev, "device not recognized\n"); > + goto err4; > + } > + > + err = kxtj9_input_init(tj9); > + if (err < 0) > + goto err4; > + > + kxtj9_device_power_off(tj9); > + atomic_set(&tj9->enabled, 0); > + > + if (client->irq) { > + err = request_threaded_irq(client->irq, NULL, kxtj9_isr, > + IRQF_TRIGGER_RISING | IRQF_ONESHOT, "kxtj9-irq", tj9); > + if (err < 0) { > + pr_err("%s: request irq failed: %d\n", __func__, err); > + goto err5; > + } > + } > + > + mutex_unlock(&tj9->lock); > + > + return 0; > + > +err5: > + kxtj9_input_cleanup(tj9); > +err4: > + kxtj9_device_power_off(tj9); > +err3: > + sysfs_remove_group(&client->dev.kobj, &kxtj9_attribute_group); > +err2: > + if (tj9->pdata.exit) > + tj9->pdata.exit(); > +err1: > + mutex_unlock(&tj9->lock); > +err0: > + kfree(tj9); > + return err; > +} > + > +static int __devexit kxtj9_remove(struct i2c_client *client) > +{ > + struct kxtj9_data *tj9 = i2c_get_clientdata(client); > + > + disable_irq_nosync(client->irq); > + free_irq(client->irq, tj9); > + kxtj9_input_cleanup(tj9); > + kxtj9_device_power_off(tj9); > + if (tj9->pdata.exit) > + tj9->pdata.exit(); > + sysfs_remove_group(&client->dev.kobj, &kxtj9_attribute_group); > + kfree(tj9); > + > + return 0; > +} > + > +#ifdef CONFIG_PM > +static int kxtj9_resume(struct i2c_client *client) > +{ > + return kxtj9_enable(i2c_get_clientdata(client)); > +} > + > +static int kxtj9_suspend(struct i2c_client *client, pm_message_t mesg) > +{ > + return kxtj9_disable(i2c_get_clientdata(client)); > +} > +#endif > + > +static const struct i2c_device_id kxtj9_id[] = { > + {NAME, 0}, > + {}, > +}; > + > +MODULE_DEVICE_TABLE(i2c, kxtj9_id); > + > +static struct i2c_driver kxtj9_driver = { > + .driver = { > + .name = NAME, > + }, > + .probe = kxtj9_probe, > + .remove = __devexit_p(kxtj9_remove), > + .resume = kxtj9_resume, > + .suspend = kxtj9_suspend, > + .id_table = kxtj9_id, > +}; > + > +static int __init kxtj9_init(void) > +{ > + return i2c_add_driver(&kxtj9_driver); > +} > + > +static void __exit kxtj9_exit(void) > +{ > + i2c_del_driver(&kxtj9_driver); > +} > + > +module_init(kxtj9_init); > +module_exit(kxtj9_exit); > + > +MODULE_DESCRIPTION("KXTJ9 accelerometer driver"); > +MODULE_AUTHOR("Chris Hudson <chudson@xxxxxxxxxx>"); > +MODULE_LICENSE("GPL"); > diff --git a/include/linux/input/kxtj9.h b/include/linux/input/kxtj9.h > new file mode 100644 > index 0000000..55c4b57 > --- /dev/null > +++ b/include/linux/input/kxtj9.h > @@ -0,0 +1,90 @@ > +/* > + * Copyright (C) 2011 Kionix, Inc. > + * Written by Chris Hudson <chudson@xxxxxxxxxx> > + * > + * 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., 59 Temple Place, Suite 330, Boston, MA > + * 02111-1307, USA > + */ > + > +#ifndef __KXTJ9_H__ > +#define __KXTJ9_H__ > + > +#define KXTJ9_I2C_ADDR 0x0F > + > +/* These shift values are used to provide consistent output data */ > +#define SHIFT_ADJ_2G 4 > +#define SHIFT_ADJ_4G 3 > +#define SHIFT_ADJ_8G 2 > + > +#ifdef __KERNEL__ > +struct kxtj9_platform_data { > + int poll_interval; /* current poll interval (in milli-seconds) */ > + int min_interval; /* minimum poll interval (in milli-seconds) */ > + > + /* by default, x is axis 0, y is axis 1, z is axis 2; these can be > + changed to account for sensor orientation within the host device */ > + u8 axis_map_x; > + u8 axis_map_y; > + u8 axis_map_z; > + > + /* each axis can be negated to account for sensor orientation within > + the host device; 1 = negate this axis; 0 = do not negate this axis */ > + u8 negate_x; > + u8 negate_y; > + u8 negate_z; > + > + /* CTRL_REG1: set resolution, g-range, data ready enable */ > + /* Output resolution: 8-bit valid or 12-bit valid */ > + #define RES_8BIT 0 > + #define RES_12BIT (1 << 6) > + u8 res_12bit; > + /* Output g-range: +/-2g, 4g, or 8g */ > + #define KXTJ9_G_2G 0 > + #define KXTJ9_G_4G (1 << 3) > + #define KXTJ9_G_8G (1 << 4) > + u8 g_range; > + /* Data ready funtion enable bit */ > + #define DRDYE (1 << 5) > + u8 drdy_enable; > + > + /* INT_CTRL_REG1: set interrupt pin enable, polarity, and response */ > + /* Interrupt pin response: set = pulse mode; unset = latch mode */ > + #define KXTJ9_IEL (1 << 3) > + u8 int_response; > + /* Interrupt pin polarity: set = active high; unset = active low */ > + #define KXTJ9_IEA (1 << 4) > + u8 int_polarity; > + /* Interrupt pin enable: set = enable; unset = disable */ > + #define KXTJ9_IEN (1 << 5) > + u8 int_enable; > + > + /* DATA_CTRL_REG: controls the output data rate of the part */ > + #define ODR12_5F 0 > + #define ODR25F 1 > + #define ODR50F 2 > + #define ODR100F 3 > + #define ODR200F 4 > + #define ODR400F 5 > + #define ODR800F 6 > + u8 data_odr_init; > + > + int (*init)(void); > + void (*exit)(void); > + int (*power_on)(void); > + int (*power_off)(void); > +}; > +#endif /* __KERNEL__ */ > + > +#endif /* __KXTJ9_H__ */ > + -- 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