/* kionix-kxsd9.c * * G-Sensor found in HTC Raphael (Touch Pro) and HTC Diamond mobile phones * * also acts as pedometer and free-fall detector * reports g-force as x,y,z * reports step count ^ +y | ___ -x | -'| +x <--| |--> |___| / -z |_O_|/ -y | / v / +z * TODO: calibration, accuracy/sensitivity, report free fall, .. * * Job Bolle */ #include #include #include #include #include #include #include #include #ifdef CONFIG_ANDROID_POWER #include #endif #define MODULE_NAME "kionix-kxsd9" #define KXSD9_DEBUG 1 #define KXSD9_DUMP 0 struct kxsd9 { struct i2c_client *client; struct input_dev *inputdev; struct hrtimer timer; struct delayed_work work; struct mutex lock; #ifdef CONFIG_ANDROID_POWER android_suspend_lock_t suspend_lock; #endif int on; struct kxsd9_seq *seq; int iseq,nseq; int pedo_up,pedo_lim; unsigned short pedo_count; }; struct kxsd9_seq { int len; unsigned char *data; } kxsd9_enable_seq[] = { { 2, "\x0d""\xc0" }, { 1, "\x1e" }, { 2, "\x0a""\xca" }, { 2, "\x0b""\x60" }, { 2, "\x0c""\xe3" }, { 1, "\x00" }, }, kxsd9_disable_seq[] = { { 2, "\x0d""\x00" }, }; static ktime_t kxsd9_poll_time = {.tv.nsec = 100 * NSEC_PER_MSEC }; static ktime_t kxsd9_seq_time = {.tv.nsec = 5 * NSEC_PER_MSEC }; int kxsd9_enable(struct kxsd9 *kxsd9,int on) { #if KXSD9_DEBUG printk(KERN_INFO MODULE_NAME ": %s" "abling accelerometer\n", on ? "En" : "Dis"); #endif if (!kxsd9->seq && !kxsd9->on) { hrtimer_start(&kxsd9->timer, kxsd9_seq_time, HRTIMER_MODE_REL); } kxsd9->on = on; kxsd9->iseq = 0; if (on) { kxsd9->nseq = ARRAY_SIZE(kxsd9_enable_seq); kxsd9->seq = kxsd9_enable_seq; } else { kxsd9->nseq = ARRAY_SIZE(kxsd9_disable_seq); kxsd9->seq = kxsd9_disable_seq; } return 0; } static int kxsd9_i2c_read(struct i2c_client *client, unsigned id, char *buf, int len) { int r; char outbuffer[2] = { 0, 0 }; outbuffer[0] = id; // maejrep: Have to separate the "ask" and "read" chunks r = i2c_master_send(client, outbuffer, 1); if (r < 0) { printk(KERN_WARNING "%s: error asking for gsensor data at " "address %02x,%02x: %d\n", __func__, client->addr, id, r); return r; } mdelay(1); r = i2c_master_recv(client, buf, len); if (r < 0) { printk(KERN_ERR "%s: error reading gsensor data at " "address %02x,%02x: %d\n", __func__, client->addr, id, r); return r; } return 0; } static enum hrtimer_restart kxsd9_poll_timer(struct hrtimer *timer) { struct kxsd9 *kxsd9; kxsd9 = container_of(timer, struct kxsd9, timer); #ifdef CONFIG_ANDROID_POWER android_lock_suspend(&kxsd9->suspend_lock); #endif schedule_work(&kxsd9->work.work); return HRTIMER_NORESTART; } static void kxsd9_work(struct work_struct *work) { struct kxsd9 *kxsd9; int err; char buf[6]; int x,y,z; unsigned long long gabs; kxsd9 = container_of(work, struct kxsd9, work.work); mutex_lock(&kxsd9->lock); if (kxsd9->seq) { #if KXSD9_DEBUG printk(KERN_INFO MODULE_NAME ": Sequence iseq %d/%d\n", kxsd9->iseq, kxsd9->nseq); #endif err = i2c_master_send(kxsd9->client, kxsd9->seq[kxsd9->iseq].data, kxsd9->seq[kxsd9->iseq].len); if (err < 0) { printk(KERN_WARNING MODULE_NAME ": %s: error %d\n", __func__, err); } if (++kxsd9->iseq >= kxsd9->nseq) kxsd9->seq = 0; // sequence finished hrtimer_start(&kxsd9->timer, kxsd9_seq_time, HRTIMER_MODE_REL); } else { err = kxsd9_i2c_read(kxsd9->client, 0, buf, 6); x = 0x8000 - 0x100 * buf[2] - buf[3]; y = 0x8000 - 0x100 * buf[0] - buf[1]; z = buf[4] * 0x100 + buf[5] - 0x8000 - 1000; // calib? gabs = x * x + y * y + z * z; if (kxsd9->pedo_up) { if (gabs > kxsd9->pedo_lim) { kxsd9->pedo_up = 0; kxsd9->pedo_lim = gabs / 2; kxsd9->pedo_count++; input_report_abs(kxsd9->inputdev, ABS_GAS, kxsd9->pedo_count); } else if (kxsd9->pedo_lim > gabs * 2) { kxsd9->pedo_lim = gabs * 2; } } else { if (gabs < kxsd9->pedo_lim) { kxsd9->pedo_up = 1; kxsd9->pedo_lim = gabs * 2; } else if (kxsd9->pedo_lim < gabs / 2) { kxsd9->pedo_lim = gabs / 2; } } #if KXSD9_DUMP #if 1 printk(KERN_INFO "G=(%6d, %6d, %6d) P=%d %s\n", x, y, z, kxsd9->pedo_count, gabs < 0x400000 ? "FF" : ""); // free-fall #else printk(KERN_INFO "G=( %02X %02X %02X %02X %02X %02X )\n", buf[0],buf[1],buf[2],buf[3],buf[4],buf[5]); #endif #endif input_report_abs(kxsd9->inputdev, ABS_X, x); input_report_abs(kxsd9->inputdev, ABS_Y, y); input_report_abs(kxsd9->inputdev, ABS_Z, z); input_sync(kxsd9->inputdev); if (kxsd9->on) hrtimer_start(&kxsd9->timer, kxsd9_poll_time, HRTIMER_MODE_REL); #ifdef CONFIG_ANDROID_POWER android_unlock_suspend(&kxsd9->suspend_lock); #endif } mutex_unlock(&kxsd9->lock); } static int kxsd9_probe(struct i2c_client *client, const struct i2c_device_id *id) { struct kxsd9 *kxsd9; struct input_dev *idev; printk(KERN_INFO MODULE_NAME ": Initializing Kionix KXSD9 driver " "at addr: 0x%02x\n", client->addr); if (!i2c_check_functionality(client->adapter, I2C_FUNC_SMBUS_BYTE_DATA)) { printk(KERN_ERR MODULE_NAME ": i2c bus not supported\n"); return -EINVAL; } kxsd9 = kzalloc(sizeof *kxsd9, GFP_KERNEL); if (kxsd9 < 0) { printk(KERN_ERR MODULE_NAME ": Not enough memory\n"); return -ENOMEM; } mutex_init(&kxsd9->lock); kxsd9->client = client; i2c_set_clientdata(client, kxsd9); idev = input_allocate_device(); if (idev) { idev->name = MODULE_NAME; set_bit(EV_ABS, idev->evbit); input_set_abs_params(idev, ABS_X, -32768, 32767, 0, 0); input_set_abs_params(idev, ABS_Y, -32768, 32767, 0, 0); input_set_abs_params(idev, ABS_Z, -32768, 32767, 0, 0); input_set_abs_params(idev, ABS_GAS, 0, 65535, 0, 0); if (!input_register_device(idev)) { kxsd9->inputdev = idev; } else { kxsd9->inputdev = 0; } } #ifdef CONFIG_ANDROID_POWER kxsd9->suspend_lock.name = MODULE_NAME; android_init_suspend_lock(&kxsd9->suspend_lock); android_lock_suspend(&kxsd9->suspend_lock); #endif INIT_DELAYED_WORK(&kxsd9->work, kxsd9_work); hrtimer_init(&kxsd9->timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL); kxsd9->timer.function = kxsd9_poll_timer; kxsd9_enable(kxsd9,1); return 0; } static int kxsd9_remove(struct i2c_client * client) { struct kxsd9 *kxsd9 = i2c_get_clientdata(client); input_unregister_device(kxsd9->inputdev); input_free_device(kxsd9->inputdev); #ifdef CONFIG_ANDROID_POWER android_uninit_suspend_lock(&kxsd9->suspend_lock); #endif kfree(kxsd9); return 0; } #if CONFIG_PM static int kxsd9_suspend(struct i2c_client * client, pm_message_t mesg) { #if KXSD9_DEBUG printk(KERN_INFO MODULE_NAME ": suspending device...\n"); #endif return 0; } static int kxsd9_resume(struct i2c_client * client) { #if KXSD9_DEBUG printk(KERN_INFO MODULE_NAME ": resuming device...\n"); #endif return 0; } #else #define kxsd9_suspend NULL #define kxsd9_resume NULL #endif static const struct i2c_device_id kxsd9_ids[] = { { MODULE_NAME, 0 }, { } }; static struct i2c_driver kxsd9_driver = { .driver = { .name = MODULE_NAME, .owner = THIS_MODULE, }, .id_table = kxsd9_ids, .probe = kxsd9_probe, .remove = kxsd9_remove, #if CONFIG_PM .suspend = kxsd9_suspend, .resume = kxsd9_resume, #endif }; static int __init kxsd9_init(void) { printk(KERN_INFO MODULE_NAME ": Registering Kionix KXSD9 driver\n"); return i2c_add_driver(&kxsd9_driver); } static void __exit kxsd9_exit(void) { printk(KERN_INFO MODULE_NAME ": Unregistered Kionix KXSD9 driver\n"); i2c_del_driver(&kxsd9_driver); } MODULE_AUTHOR("Job Bolle"); MODULE_DESCRIPTION("Kionix-KXSD9 Driver"); MODULE_LICENSE("GPL"); module_init(kxsd9_init); module_exit(kxsd9_exit);