/* drivers/i2c/chips/akm8973.c - akm8973 compass driver * * Copyright (C) 2008-2009 HTC Corporation. * * This software is licensed under the terms of the GNU General Public * License version 2, as published by the Free Software Foundation, and * may be copied, distributed, and modified under those terms. * * 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. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #define DEBUG 0 #define MAX_FAILURE_COUNT 3 static struct i2c_client *this_client; struct akm8973_data { struct input_dev *input_dev; struct work_struct work; struct early_suspend early_suspend_akm; }; /* Addresses to scan -- protected by sense_data_mutex */ static char sense_data[RBUFF_SIZE + 1]; static struct mutex sense_data_mutex; #define AKM8973_RETRY_COUNT 10 static DECLARE_WAIT_QUEUE_HEAD(data_ready_wq); static DECLARE_WAIT_QUEUE_HEAD(open_wq); static atomic_t data_ready; static atomic_t open_count; static atomic_t open_flag; static atomic_t reserve_open_flag; static atomic_t m_flag; static atomic_t a_flag; static atomic_t t_flag; static atomic_t mv_flag; static int failure_count = 0; static short akmd_delay = 0; static atomic_t suspend_flag = ATOMIC_INIT(0); static struct akm8973_platform_data *pdata; static int AKI2C_RxData(char *rxData, int length) { uint8_t loop_i; struct i2c_msg msgs[] = { { .addr = this_client->addr, .flags = 0, .len = 1, .buf = rxData, }, { .addr = this_client->addr, .flags = I2C_M_RD, .len = length, .buf = rxData, }, }; for (loop_i = 0; loop_i < AKM8973_RETRY_COUNT; loop_i++) { if (i2c_transfer(this_client->adapter, msgs, 2) > 0) { break; } mdelay(10); } if (loop_i >= AKM8973_RETRY_COUNT) { printk(KERN_ERR "%s retry over %d\n", __func__, AKM8973_RETRY_COUNT); return -EIO; } return 0; } static int AKI2C_TxData(char *txData, int length) { uint8_t loop_i; struct i2c_msg msg[] = { { .addr = this_client->addr, .flags = 0, .len = length, .buf = txData, }, }; for (loop_i = 0; loop_i < AKM8973_RETRY_COUNT; loop_i++) { if (i2c_transfer(this_client->adapter, msg, 1) > 0) { break; } mdelay(10); } if (loop_i >= AKM8973_RETRY_COUNT) { printk(KERN_ERR "%s retry over %d\n", __func__, AKM8973_RETRY_COUNT); return -EIO; } return 0; } static void AKECS_Reset(void) { gpio_set_value(pdata->reset, 0); udelay(120); gpio_set_value(pdata->reset, 1); } static int AKECS_StartMeasure(void) { char buffer[2]; atomic_set(&data_ready, 0); /* Set measure mode */ buffer[0] = AKECS_REG_MS1; buffer[1] = AKECS_MODE_MEASURE; /* Set data */ return AKI2C_TxData(buffer, 2); } static int AKECS_PowerDown(void) { char buffer[2]; int ret; /* Set powerdown mode */ buffer[0] = AKECS_REG_MS1; buffer[1] = AKECS_MODE_POWERDOWN; /* Set data */ ret = AKI2C_TxData(buffer, 2); if (ret < 0) return ret; /* Dummy read for clearing INT pin */ buffer[0] = AKECS_REG_TMPS; /* Read data */ ret = AKI2C_RxData(buffer, 1); if (ret < 0) return ret; return ret; } static int AKECS_StartE2PRead(void) { char buffer[2]; /* Set measure mode */ buffer[0] = AKECS_REG_MS1; buffer[1] = AKECS_MODE_E2P_READ; /* Set data */ return AKI2C_TxData(buffer, 2); } static int AKECS_GetData(void) { char buffer[RBUFF_SIZE + 1]; int ret; memset(buffer, 0, RBUFF_SIZE + 1); buffer[0] = AKECS_REG_ST; ret = AKI2C_RxData(buffer, RBUFF_SIZE+1); if (ret < 0) return ret; mutex_lock(&sense_data_mutex); memcpy(sense_data, buffer, sizeof(buffer)); atomic_set(&data_ready, 1); wake_up(&data_ready_wq); mutex_unlock(&sense_data_mutex); return 0; } static int AKECS_SetMode(char mode) { int ret; switch (mode) { case AKECS_MODE_MEASURE: ret = AKECS_StartMeasure(); break; case AKECS_MODE_E2P_READ: ret = AKECS_StartE2PRead(); break; case AKECS_MODE_POWERDOWN: ret = AKECS_PowerDown(); break; default: return -EINVAL; } /* wait at least 300us after changing mode */ mdelay(1); return ret; } static int AKECS_TransRBuff(char *rbuf, int size) { wait_event_interruptible_timeout(data_ready_wq, atomic_read(&data_ready), 1000); if (!atomic_read(&data_ready)) { if (!atomic_read(&suspend_flag)) { printk(KERN_ERR "AKM8973 AKECS_TransRBUFF: Data not ready\n"); failure_count++; if (failure_count >= MAX_FAILURE_COUNT) { printk(KERN_ERR "AKM8973 AKECS_TransRBUFF: successive %d failure.\n", failure_count); atomic_set(&open_flag, -1); wake_up(&open_wq); failure_count = 0; } } return -1; } mutex_lock(&sense_data_mutex); memcpy(&rbuf[1], &sense_data[1], size); atomic_set(&data_ready, 0); mutex_unlock(&sense_data_mutex); failure_count = 0; return 0; } static void AKECS_Report_Value(short *rbuf) { struct akm8973_data *data = i2c_get_clientdata(this_client); #if DEBUG printk(KERN_INFO"AKECS_Report_Value: yaw = %d, pitch = %d, roll = %d\n", rbuf[0], rbuf[1], rbuf[2]); printk(KERN_INFO" tmp = %d, m_stat= %d, g_stat=%d\n", rbuf[3], rbuf[4], rbuf[5]); printk(KERN_INFO" G_Sensor: x = %d LSB, y = %d LSB, z = %d LSB\n", rbuf[6], rbuf[7], rbuf[8]); #endif /* Offset compass hack taken from michyprimas kernel */ short valueint = rbuf[0]; if ((valueint + 85) < 360) { rbuf[0] = valueint + 85; } else { short diff = 359 - valueint; rbuf[0] = 85 - diff; } /* Report magnetic sensor information */ if (atomic_read(&m_flag)) { input_report_abs(data->input_dev, ABS_RX, rbuf[0]); input_report_abs(data->input_dev, ABS_RY, rbuf[1]); input_report_abs(data->input_dev, ABS_RZ, rbuf[2]); input_report_abs(data->input_dev, ABS_RUDDER, rbuf[4]); } /* Report acceleration sensor information */ if (atomic_read(&a_flag)) { input_report_abs(data->input_dev, ABS_X, rbuf[6]); input_report_abs(data->input_dev, ABS_Y, rbuf[7]); input_report_abs(data->input_dev, ABS_Z, rbuf[8]); input_report_abs(data->input_dev, ABS_WHEEL, rbuf[5]); } /* Report temperature information */ if (atomic_read(&t_flag)) input_report_abs(data->input_dev, ABS_THROTTLE, rbuf[3]); if (atomic_read(&mv_flag)) { input_report_abs(data->input_dev, ABS_HAT0X, rbuf[9]); input_report_abs(data->input_dev, ABS_HAT0Y, rbuf[10]); input_report_abs(data->input_dev, ABS_BRAKE, rbuf[11]); } input_sync(data->input_dev); } static int AKECS_GetOpenStatus(void) { wait_event_interruptible(open_wq, (atomic_read(&open_flag) != 0)); return atomic_read(&open_flag); } static int AKECS_GetCloseStatus(void) { wait_event_interruptible(open_wq, (atomic_read(&open_flag) <= 0)); return atomic_read(&open_flag); } static void AKECS_CloseDone(void) { atomic_set(&m_flag, 0); atomic_set(&a_flag, 0); atomic_set(&t_flag, 0); atomic_set(&mv_flag, 0); } static int akm_aot_open(struct inode *inode, struct file *file) { int ret = -1; if (atomic_cmpxchg(&open_count, 0, 1) == 0) { if (atomic_cmpxchg(&open_flag, 0, 1) == 0) { atomic_set(&reserve_open_flag, 1); wake_up(&open_wq); ret = 0; } } return ret; } static int akm_aot_release(struct inode *inode, struct file *file) { atomic_set(&reserve_open_flag, 0); atomic_set(&open_flag, 0); atomic_set(&open_count, 0); wake_up(&open_wq); return 0; } static int akm_aot_ioctl(struct inode *inode, struct file *file, unsigned int cmd, unsigned long arg) { void __user *argp = (void __user *)arg; short flag; switch (cmd) { case ECS_IOCTL_APP_SET_MFLAG: case ECS_IOCTL_APP_SET_AFLAG: case ECS_IOCTL_APP_SET_TFLAG: case ECS_IOCTL_APP_SET_MVFLAG: if (copy_from_user(&flag, argp, sizeof(flag))) return -EFAULT; if (flag < 0 || flag > 1) return -EINVAL; break; case ECS_IOCTL_APP_SET_DELAY: if (copy_from_user(&flag, argp, sizeof(flag))) return -EFAULT; break; default: break; } switch (cmd) { case ECS_IOCTL_APP_SET_MFLAG: atomic_set(&m_flag, flag); break; case ECS_IOCTL_APP_GET_MFLAG: flag = atomic_read(&m_flag); break; case ECS_IOCTL_APP_SET_AFLAG: atomic_set(&a_flag, flag); break; case ECS_IOCTL_APP_GET_AFLAG: flag = atomic_read(&a_flag); break; case ECS_IOCTL_APP_SET_TFLAG: atomic_set(&t_flag, flag); break; case ECS_IOCTL_APP_GET_TFLAG: flag = atomic_read(&t_flag); break; case ECS_IOCTL_APP_SET_MVFLAG: atomic_set(&mv_flag, flag); break; case ECS_IOCTL_APP_GET_MVFLAG: flag = atomic_read(&mv_flag); break; case ECS_IOCTL_APP_SET_DELAY: akmd_delay = flag; break; case ECS_IOCTL_APP_GET_DELAY: flag = akmd_delay; break; default: return -ENOTTY; } switch (cmd) { case ECS_IOCTL_APP_GET_MFLAG: case ECS_IOCTL_APP_GET_AFLAG: case ECS_IOCTL_APP_GET_TFLAG: case ECS_IOCTL_APP_GET_MVFLAG: case ECS_IOCTL_APP_GET_DELAY: if (copy_to_user(argp, &flag, sizeof(flag))) return -EFAULT; break; default: break; } return 0; } static int akmd_open(struct inode *inode, struct file *file) { return nonseekable_open(inode, file); } static int akmd_release(struct inode *inode, struct file *file) { AKECS_CloseDone(); return 0; } static int akmd_ioctl(struct inode *inode, struct file *file, unsigned int cmd, unsigned long arg) { void __user *argp = (void __user *)arg; char msg[RBUFF_SIZE + 1], rwbuf[5]; int ret = -1, status; short mode, value[12], delay; char project_name[64]; short layouts[4][3][3]; int i, j, k; switch (cmd) { case ECS_IOCTL_WRITE: case ECS_IOCTL_READ: if (copy_from_user(&rwbuf, argp, sizeof(rwbuf))) return -EFAULT; break; case ECS_IOCTL_SET_MODE: if (copy_from_user(&mode, argp, sizeof(mode))) return -EFAULT; break; case ECS_IOCTL_SET_YPR: if (copy_from_user(&value, argp, sizeof(value))) return -EFAULT; break; default: break; } switch (cmd) { case ECS_IOCTL_WRITE: if (rwbuf[0] < 2) return -EINVAL; ret = AKI2C_TxData(&rwbuf[1], rwbuf[0]); if (ret < 0) return ret; break; case ECS_IOCTL_READ: if (rwbuf[0] < 1) return -EINVAL; ret = AKI2C_RxData(&rwbuf[1], rwbuf[0]); if (ret < 0) return ret; break; case ECS_IOCTL_RESET: AKECS_Reset(); break; case ECS_IOCTL_SET_MODE: ret = AKECS_SetMode((char)mode); if (ret < 0) return ret; break; case ECS_IOCTL_GETDATA: ret = AKECS_TransRBuff(msg, RBUFF_SIZE); if (ret < 0) return ret; break; case ECS_IOCTL_SET_YPR: AKECS_Report_Value(value); break; case ECS_IOCTL_GET_OPEN_STATUS: status = AKECS_GetOpenStatus(); break; case ECS_IOCTL_GET_CLOSE_STATUS: status = AKECS_GetCloseStatus(); break; case ECS_IOCTL_GET_DELAY: delay = akmd_delay; break; case ECS_IOCTL_GET_PROJECT_NAME: strncpy(project_name, pdata->project_name, 64); break; case ECS_IOCTL_GET_MATRIX: for (i = 0; i < 4; i++) for (j = 0; j < 3; j++) for (k = 0; k < 3; k++) layouts[i][j][k] = pdata->layouts[i][j][k]; break; default: return -ENOTTY; } switch (cmd) { case ECS_IOCTL_READ: if (copy_to_user(argp, &rwbuf, sizeof(rwbuf))) return -EFAULT; break; case ECS_IOCTL_GETDATA: if (copy_to_user(argp, &msg, sizeof(msg))) return -EFAULT; break; case ECS_IOCTL_GET_OPEN_STATUS: case ECS_IOCTL_GET_CLOSE_STATUS: if (copy_to_user(argp, &status, sizeof(status))) return -EFAULT; break; case ECS_IOCTL_GET_DELAY: if (copy_to_user(argp, &delay, sizeof(delay))) return -EFAULT; break; case ECS_IOCTL_GET_PROJECT_NAME: if (copy_to_user(argp, project_name, sizeof(project_name))) return -EFAULT; break; case ECS_IOCTL_GET_MATRIX: if (copy_to_user(argp, layouts, sizeof(layouts))) return -EFAULT; break; default: break; } return 0; } static void akm_work_func(struct work_struct *work) { if (AKECS_GetData() < 0) printk(KERN_ERR "AKM8973 akm_work_func: Get data failed\n"); enable_irq(this_client->irq); } static irqreturn_t akm8973_interrupt(int irq, void *dev_id) { struct akm8973_data *data = dev_id; disable_irq_nosync(this_client->irq); schedule_work(&data->work); return IRQ_HANDLED; } static void akm8973_early_suspend(struct early_suspend *handler) { atomic_set(&suspend_flag, 1); atomic_set(&reserve_open_flag, atomic_read(&open_flag)); atomic_set(&open_flag, 0); wake_up(&open_wq); disable_irq(this_client->irq); } static void akm8973_early_resume(struct early_suspend *handler) { enable_irq(this_client->irq); atomic_set(&suspend_flag, 0); atomic_set(&open_flag, atomic_read(&reserve_open_flag)); wake_up(&open_wq); } static struct file_operations akmd_fops = { .owner = THIS_MODULE, .open = akmd_open, .release = akmd_release, .ioctl = akmd_ioctl, }; static struct file_operations akm_aot_fops = { .owner = THIS_MODULE, .open = akm_aot_open, .release = akm_aot_release, .ioctl = akm_aot_ioctl, }; static struct miscdevice akm_aot_device = { .minor = MISC_DYNAMIC_MINOR, .name = "akm8973_aot", .fops = &akm_aot_fops, }; static struct miscdevice akmd_device = { .minor = MISC_DYNAMIC_MINOR, .name = "akm8973_daemon", .fops = &akmd_fops, }; int akm8973_probe(struct i2c_client *client, const struct i2c_device_id *id) { struct akm8973_data *akm; int err = 0; if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) { err = -ENODEV; goto exit_check_functionality_failed; } akm = kzalloc(sizeof(struct akm8973_data), GFP_KERNEL); if (!akm) { err = -ENOMEM; goto exit_alloc_data_failed; } INIT_WORK(&akm->work, akm_work_func); i2c_set_clientdata(client, akm); pdata = client->dev.platform_data; if (pdata == NULL) { printk(KERN_ERR"AKM8973 akm8973_probe: platform data is NULL\n"); goto exit_platform_data_null; } this_client = client; if (pdata && pdata->reset) { err = gpio_request(pdata->reset, "akm8973"); if (err < 0) { printk(KERN_ERR "%s: request reset gpio failed\n", __func__); goto err_request_reset_gpio; } err = gpio_direction_output(pdata->reset, 1); if (err < 0) { printk(KERN_ERR "%s: request reset gpio failed\n", __func__); goto err_set_reset_gpio; } } else { printk(KERN_ERR "%s: pdata or pdata->reset is NULL\n", __func__); goto err_request_reset_gpio; } err = AKECS_PowerDown(); if (err < 0) { printk(KERN_ERR"AKM8973 akm8973_probe: set power down mode error\n"); goto exit_set_mode_failed; } err = request_irq(client->irq, akm8973_interrupt, IRQF_TRIGGER_HIGH, "akm8973", akm); if (err < 0) { printk(KERN_ERR"AKM8973 akm8973_probe: request irq failed\n"); goto exit_irq_request_failed; } akm->input_dev = input_allocate_device(); if (!akm->input_dev) { err = -ENOMEM; printk(KERN_ERR "AKM8973 akm8973_probe: Failed to allocate input device\n"); goto exit_input_dev_alloc_failed; } set_bit(EV_ABS, akm->input_dev->evbit); /* yaw */ input_set_abs_params(akm->input_dev, ABS_RX, 0, 360, 0, 0); /* pitch */ input_set_abs_params(akm->input_dev, ABS_RY, -180, 180, 0, 0); /* roll */ input_set_abs_params(akm->input_dev, ABS_RZ, -90, 90, 0, 0); /* x-axis acceleration */ input_set_abs_params(akm->input_dev, ABS_X, -1872, 1872, 0, 0); /* y-axis acceleration */ input_set_abs_params(akm->input_dev, ABS_Y, -1872, 1872, 0, 0); /* z-axis acceleration */ input_set_abs_params(akm->input_dev, ABS_Z, -1872, 1872, 0, 0); /* temparature */ input_set_abs_params(akm->input_dev, ABS_THROTTLE, -30, 85, 0, 0); /* status of magnetic sensor */ input_set_abs_params(akm->input_dev, ABS_RUDDER, -32768, 3, 0, 0); /* status of acceleration sensor */ input_set_abs_params(akm->input_dev, ABS_WHEEL, -32768, 3, 0, 0); /* step count */ input_set_abs_params(akm->input_dev, ABS_GAS, 0, 65535, 0, 0); /* x-axis of raw magnetic vector */ input_set_abs_params(akm->input_dev, ABS_HAT0X, -2048, 2032, 0, 0); /* y-axis of raw magnetic vector */ input_set_abs_params(akm->input_dev, ABS_HAT0Y, -2048, 2032, 0, 0); /* z-axis of raw magnetic vector */ input_set_abs_params(akm->input_dev, ABS_BRAKE, -2048, 2032, 0, 0); akm->input_dev->name = "compass"; err = input_register_device(akm->input_dev); if (err) { printk(KERN_ERR "AKM8973 akm8973_probe: Unable to register input device: %s\n", akm->input_dev->name); goto exit_input_register_device_failed; } err = misc_register(&akmd_device); if (err) { printk(KERN_ERR "AKM8973 akm8973_probe: akmd_device register failed\n"); goto exit_misc_device_register_failed; } err = misc_register(&akm_aot_device); if (err) { printk(KERN_ERR "AKM8973 akm8973_probe: akm_aot_device register failed\n"); goto exit_misc_device_register_failed; } mutex_init(&sense_data_mutex); init_waitqueue_head(&data_ready_wq); init_waitqueue_head(&open_wq); /* As default, do not report all information */ atomic_set(&m_flag, 0); atomic_set(&a_flag, 0); atomic_set(&t_flag, 0); atomic_set(&mv_flag, 0); akm->early_suspend_akm.suspend = akm8973_early_suspend; akm->early_suspend_akm.resume = akm8973_early_resume; register_early_suspend(&akm->early_suspend_akm); return 0; exit_misc_device_register_failed: exit_input_register_device_failed: input_free_device(akm->input_dev); exit_input_dev_alloc_failed: free_irq(client->irq, akm); exit_irq_request_failed: exit_set_mode_failed: err_set_reset_gpio: gpio_free(pdata->reset); err_request_reset_gpio: exit_platform_data_null: kfree(akm); exit_alloc_data_failed: exit_check_functionality_failed: return err; } static int akm8973_remove(struct i2c_client *client) { struct akm8973_data *akm = i2c_get_clientdata(client); free_irq(client->irq, akm); input_unregister_device(akm->input_dev); kfree(akm); if (pdata && pdata->reset) gpio_free(pdata->reset); return 0; } static const struct i2c_device_id akm8973_id[] = { { AKM8973_I2C_NAME, 0 }, { } }; static struct i2c_driver akm8973_driver = { .probe = akm8973_probe, .remove = akm8973_remove, .id_table = akm8973_id, .driver = { .name = AKM8973_I2C_NAME, }, }; static int __init akm8973_init(void) { printk(KERN_INFO "AKM8973 compass driver: init\n"); return i2c_add_driver(&akm8973_driver); } static void __exit akm8973_exit(void) { i2c_del_driver(&akm8973_driver); } module_init(akm8973_init); module_exit(akm8973_exit); MODULE_AUTHOR("viral wang "); MODULE_DESCRIPTION("AKM8973 compass driver"); MODULE_LICENSE("GPL");