| /* drivers/i2c/chips/bma250.c - bma250 G-sensor 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 <linux/i2c.h> |
| #include <linux/slab.h> |
| #include <linux/miscdevice.h> |
| #include <linux/uaccess.h> |
| #include <linux/input.h> |
| #include <linux/bma250.h> |
| #include <linux/gpio.h> |
| #include <linux/delay.h> |
| #include<linux/earlysuspend.h> |
| #include <linux/export.h> |
| #include <linux/module.h> |
| |
| |
| #define D(x...) pr_info("[GSNR][BMA250] " x) |
| #define E(x...) printk(KERN_ERR "[GSNR][BMA250 ERROR] " x) |
| #define DIF(x...) {\ |
| if (debug_flag)\ |
| printk(KERN_DEBUG "[GSNR][BMA250 DEBUG] " x); } |
| |
| #define DEFAULT_RANGE BMA_RANGE_2G |
| #define DEFAULT_BW BMA_BW_31_25HZ |
| |
| #define RETRY_TIMES 10 |
| |
| static struct i2c_client *this_client; |
| |
| struct bma250_data { |
| struct input_dev *input_dev; |
| struct work_struct work; |
| struct early_suspend early_suspend; |
| }; |
| |
| static struct bma250_platform_data *pdata; |
| static atomic_t PhoneOn_flag = ATOMIC_INIT(0); |
| #define DEVICE_ACCESSORY_ATTR(_name, _mode, _show, _store) \ |
| struct device_attribute dev_attr_##_name = __ATTR(_name, _mode, _show, _store) |
| |
| static int debug_flag; |
| static char update_user_calibrate_data; |
| |
| static int BMA_I2C_RxData(char *rxData, int length) |
| { |
| int retry; |
| 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 (retry = 0; retry <= RETRY_TIMES; retry++) { |
| if (i2c_transfer(this_client->adapter, msgs, 2) > 0) |
| break; |
| else |
| mdelay(10); |
| } |
| |
| if (retry > RETRY_TIMES) { |
| E("%s: retry over %d\n", __func__, RETRY_TIMES); |
| return -EIO; |
| } else |
| return 0; |
| } |
| |
| static int BMA_I2C_TxData(char *txData, int length) |
| { |
| int retry; |
| struct i2c_msg msg[] = { |
| { |
| .addr = this_client->addr, |
| .flags = 0, |
| .len = length, |
| .buf = txData, |
| }, |
| }; |
| |
| for (retry = 0; retry <= RETRY_TIMES; retry++) { |
| if (i2c_transfer(this_client->adapter, msg, 1) > 0) |
| break; |
| else |
| mdelay(10); |
| } |
| |
| if (retry > RETRY_TIMES) { |
| E("%s: retry over %d\n", __func__, RETRY_TIMES); |
| return -EIO; |
| } else |
| return 0; |
| } |
| |
| static int BMA_Init(void) |
| { |
| char buffer[4] = ""; |
| int ret; |
| unsigned char range = 0, bw = 0; |
| |
| memset(buffer, 0, 4); |
| |
| buffer[0] = bma250_RANGE_SEL_REG; |
| ret = BMA_I2C_RxData(buffer, 2); |
| if (ret < 0) |
| return -1; |
| D("%s: bma250_RANGE_SEL_REG++: range = 0x%02x, bw = 0x%02x\n", |
| __func__, buffer[0], buffer[1]); |
| range = (buffer[0] & 0xF0) | DEFAULT_RANGE; |
| bw = (buffer[1] & 0xE0) | DEFAULT_BW; |
| |
| |
| |
| buffer[1] = bw; |
| buffer[0] = bma250_BW_SEL_REG; |
| ret = BMA_I2C_TxData(buffer, 2); |
| if (ret < 0) { |
| E("%s: Write bma250_BW_SEL_REG fail\n", __func__); |
| return -1; |
| } |
| |
| buffer[1] = range; |
| buffer[0] = bma250_RANGE_SEL_REG; |
| ret = BMA_I2C_TxData(buffer, 2); |
| if (ret < 0) { |
| E("%s: Write bma250_BW_SEL_REG fail\n", __func__); |
| return -1; |
| } |
| |
| |
| buffer[0] = bma250_RANGE_SEL_REG; |
| ret = BMA_I2C_RxData(buffer, 2); |
| if (ret < 0) |
| return -1; |
| |
| D("%s: bma250_RANGE_SEL_REG:use single write--: range = 0x%02x, bw = 0x%02x\n", |
| __func__, buffer[0], buffer[1]); |
| |
| return 0; |
| |
| } |
| |
| static int BMA_TransRBuff(short *rbuf) |
| { |
| char buffer[6]; |
| int ret; |
| |
| memset(buffer, 0, 6); |
| |
| buffer[0] = bma250_X_AXIS_LSB_REG; |
| ret = BMA_I2C_RxData(buffer, 6); |
| if (ret < 0) |
| return ret; |
| |
| |
| rbuf[0] = (short)(buffer[1] << 8 | buffer[0]); |
| rbuf[0] >>= 6; |
| rbuf[1] = (short)(buffer[3] << 8 | buffer[2]); |
| rbuf[1] >>= 6; |
| rbuf[2] = (short)(buffer[5] << 8 | buffer[4]); |
| rbuf[2] >>= 6; |
| |
| DIF("%s: (x, y, z) = (%d, %d, %d)\n", |
| __func__, rbuf[0], rbuf[1], rbuf[2]); |
| |
| return 1; |
| } |
| |
| static int BMA_set_mode(unsigned char mode) |
| { |
| char buffer[2] = ""; |
| int ret = 0; |
| unsigned char data1 = 0; |
| |
| printk(KERN_INFO "[GSNR] Gsensor %s\n", mode ? "disable" : "enable"); |
| |
| memset(buffer, 0, 2); |
| |
| D("%s: mode = 0x%02x\n", __func__, mode); |
| if (mode < 2) { |
| buffer[0] = bma250_MODE_CTRL_REG; |
| ret = BMA_I2C_RxData(buffer, 1); |
| if (ret < 0) |
| return -1; |
| |
| |
| switch (mode) { |
| case bma250_MODE_NORMAL: |
| data1 = buffer[0] & 0x7F; |
| break; |
| case bma250_MODE_SUSPEND: |
| data1 = buffer[0] | 0x80; |
| break; |
| default: |
| break; |
| } |
| |
| |
| buffer[0] = bma250_MODE_CTRL_REG; |
| buffer[1] = data1; |
| ret = BMA_I2C_TxData(buffer, 2); |
| } else |
| ret = E_OUT_OF_RANGE; |
| |
| if (mode == bma250_MODE_NORMAL) |
| usleep(2000); |
| |
| |
| return ret; |
| } |
| |
| static int BMA_GET_INT(void) |
| { |
| int ret; |
| ret = gpio_get_value(pdata->intr); |
| return ret; |
| } |
| |
| static int bma_open(struct inode *inode, struct file *file) |
| { |
| return nonseekable_open(inode, file); |
| } |
| |
| static int bma_release(struct inode *inode, struct file *file) |
| { |
| return 0; |
| } |
| |
| static long bma_ioctl(struct file *file, unsigned int cmd, |
| unsigned long arg) |
| { |
| |
| void __user *argp = (void __user *)arg; |
| |
| char rwbuf[8] = ""; |
| int ret = -1; |
| short buf[8], temp; |
| int kbuf = 0; |
| |
| DIF("%s: cmd = 0x%x\n", __func__, cmd); |
| |
| switch (cmd) { |
| case BMA_IOCTL_READ: |
| case BMA_IOCTL_WRITE: |
| case BMA_IOCTL_SET_MODE: |
| case BMA_IOCTL_SET_CALI_MODE: |
| case BMA_IOCTL_SET_UPDATE_USER_CALI_DATA: |
| if (copy_from_user(&rwbuf, argp, sizeof(rwbuf))) |
| return -EFAULT; |
| break; |
| case BMA_IOCTL_READ_ACCELERATION: |
| if (copy_from_user(&buf, argp, sizeof(buf))) |
| return -EFAULT; |
| break; |
| case BMA_IOCTL_WRITE_CALI_VALUE: |
| if (copy_from_user(&kbuf, argp, sizeof(kbuf))) |
| return -EFAULT; |
| break; |
| default: |
| break; |
| } |
| |
| switch (cmd) { |
| case BMA_IOCTL_INIT: |
| ret = BMA_Init(); |
| if (ret < 0) |
| return ret; |
| break; |
| case BMA_IOCTL_READ: |
| if (rwbuf[0] < 1) |
| return -EINVAL; |
| break; |
| case BMA_IOCTL_WRITE: |
| if (rwbuf[0] < 2) |
| return -EINVAL; |
| break; |
| case BMA_IOCTL_WRITE_CALI_VALUE: |
| pdata->gs_kvalue = kbuf; |
| printk(KERN_INFO "%s: Write calibration value: 0x%X\n", |
| __func__, pdata->gs_kvalue); |
| break; |
| case BMA_IOCTL_READ_ACCELERATION: |
| ret = BMA_TransRBuff(&buf[0]); |
| if (ret < 0) |
| return ret; |
| break; |
| case BMA_IOCTL_READ_CALI_VALUE: |
| if ((pdata->gs_kvalue & (0x67 << 24)) != (0x67 << 24)) { |
| rwbuf[0] = 0; |
| rwbuf[1] = 0; |
| rwbuf[2] = 0; |
| } else { |
| rwbuf[0] = (pdata->gs_kvalue >> 16) & 0xFF; |
| rwbuf[1] = (pdata->gs_kvalue >> 8) & 0xFF; |
| rwbuf[2] = pdata->gs_kvalue & 0xFF; |
| } |
| DIF("%s: CALI(x, y, z) = (%d, %d, %d)\n", |
| __func__, rwbuf[0], rwbuf[1], rwbuf[2]); |
| break; |
| case BMA_IOCTL_SET_MODE: |
| BMA_set_mode(rwbuf[0]); |
| break; |
| case BMA_IOCTL_GET_INT: |
| temp = BMA_GET_INT(); |
| break; |
| case BMA_IOCTL_GET_CHIP_LAYOUT: |
| if (pdata) |
| temp = pdata->chip_layout; |
| break; |
| case BMA_IOCTL_GET_CALI_MODE: |
| if (pdata) |
| temp = pdata->calibration_mode; |
| break; |
| case BMA_IOCTL_SET_CALI_MODE: |
| if (pdata) |
| pdata->calibration_mode = rwbuf[0]; |
| break; |
| case BMA_IOCTL_GET_UPDATE_USER_CALI_DATA: |
| temp = update_user_calibrate_data; |
| break; |
| case BMA_IOCTL_SET_UPDATE_USER_CALI_DATA: |
| update_user_calibrate_data = rwbuf[0]; |
| break; |
| |
| default: |
| return -ENOTTY; |
| } |
| |
| switch (cmd) { |
| case BMA_IOCTL_READ: |
| break; |
| case BMA_IOCTL_READ_ACCELERATION: |
| if (copy_to_user(argp, &buf, sizeof(buf))) |
| return -EFAULT; |
| break; |
| case BMA_IOCTL_READ_CALI_VALUE: |
| if (copy_to_user(argp, &rwbuf, sizeof(rwbuf))) |
| return -EFAULT; |
| break; |
| case BMA_IOCTL_GET_INT: |
| if (copy_to_user(argp, &temp, sizeof(temp))) |
| return -EFAULT; |
| break; |
| case BMA_IOCTL_GET_CHIP_LAYOUT: |
| if (copy_to_user(argp, &temp, sizeof(temp))) |
| return -EFAULT; |
| break; |
| case BMA_IOCTL_GET_CALI_MODE: |
| if (copy_to_user(argp, &temp, sizeof(temp))) |
| return -EFAULT; |
| break; |
| case BMA_IOCTL_GET_UPDATE_USER_CALI_DATA: |
| if (copy_to_user(argp, &temp, sizeof(temp))) |
| return -EFAULT; |
| break; |
| default: |
| break; |
| } |
| |
| return 0; |
| } |
| |
| #ifdef EARLY_SUSPEND_BMA |
| |
| static void bma250_early_suspend(struct early_suspend *handler) |
| { |
| if (!atomic_read(&PhoneOn_flag)) |
| BMA_set_mode(bma250_MODE_SUSPEND); |
| else |
| printk(KERN_DEBUG "bma250_early_suspend: PhoneOn_flag is set\n"); |
| } |
| |
| static void bma250_late_resume(struct early_suspend *handler) |
| { |
| BMA_set_mode(bma250_MODE_NORMAL); |
| } |
| |
| #else |
| |
| static int bma250_suspend(struct i2c_client *client, pm_message_t mesg) |
| { |
| BMA_set_mode(bma250_MODE_SUSPEND); |
| |
| return 0; |
| } |
| |
| static int bma250_resume(struct i2c_client *client) |
| { |
| BMA_set_mode(bma250_MODE_NORMAL); |
| return 0; |
| } |
| #endif |
| |
| static ssize_t bma250_show(struct device *dev, |
| struct device_attribute *attr, char *buf) |
| { |
| char *s = buf; |
| s += sprintf(s, "%d\n", atomic_read(&PhoneOn_flag)); |
| return s - buf; |
| } |
| |
| static ssize_t bma250_store(struct device *dev, |
| struct device_attribute *attr, |
| const char *buf, size_t count) |
| { |
| if (count == (strlen("enable") + 1) && |
| strncmp(buf, "enable", strlen("enable")) == 0) { |
| atomic_set(&PhoneOn_flag, 1); |
| printk(KERN_DEBUG "bma250_store: PhoneOn_flag=%d\n", |
| atomic_read(&PhoneOn_flag)); |
| return count; |
| } |
| if (count == (strlen("disable") + 1) && |
| strncmp(buf, "disable", strlen("disable")) == 0) { |
| atomic_set(&PhoneOn_flag, 0); |
| printk(KERN_DEBUG "bma250_store: PhoneOn_flag=%d\n", |
| atomic_read(&PhoneOn_flag)); |
| return count; |
| } |
| E("bma250_store: invalid argument\n"); |
| return -EINVAL; |
| |
| } |
| |
| static DEVICE_ACCESSORY_ATTR(PhoneOnOffFlag, 0664, \ |
| bma250_show, bma250_store); |
| |
| static ssize_t debug_flag_show(struct device *dev, |
| struct device_attribute *attr, char *buf) |
| { |
| char *s = buf; |
| char buffer, range = -1, bandwidth = -1, mode = -1; |
| int ret; |
| |
| buffer = bma250_BW_SEL_REG; |
| ret = BMA_I2C_RxData(&buffer, 1); |
| if (ret < 0) |
| return -1; |
| bandwidth = (buffer & 0x1F); |
| |
| buffer = bma250_RANGE_SEL_REG; |
| ret = BMA_I2C_RxData(&buffer, 1); |
| if (ret < 0) |
| return -1; |
| range = (buffer & 0xF); |
| |
| buffer = bma250_MODE_CTRL_REG; |
| ret = BMA_I2C_RxData(&buffer, 1); |
| if (ret < 0) |
| return -1; |
| mode = ((buffer & 0x80) >> 7); |
| |
| s += sprintf(s, "debug_flag = %d, range = 0x%x, bandwidth = 0x%x, " |
| "mode = 0x%x\n", debug_flag, range, bandwidth, mode); |
| |
| return s - buf; |
| } |
| static ssize_t debug_flag_store(struct device *dev, |
| struct device_attribute *attr, |
| const char *buf, size_t count) |
| { |
| debug_flag = -1; |
| sscanf(buf, "%d", &debug_flag); |
| |
| return count; |
| |
| } |
| |
| static DEVICE_ACCESSORY_ATTR(debug_en, 0664, \ |
| debug_flag_show, debug_flag_store); |
| |
| int bma250_registerAttr(void) |
| { |
| int ret; |
| struct class *htc_accelerometer_class; |
| struct device *accelerometer_dev; |
| |
| htc_accelerometer_class = class_create(THIS_MODULE, |
| "htc_accelerometer"); |
| if (IS_ERR(htc_accelerometer_class)) { |
| ret = PTR_ERR(htc_accelerometer_class); |
| htc_accelerometer_class = NULL; |
| goto err_create_class; |
| } |
| |
| accelerometer_dev = device_create(htc_accelerometer_class, |
| NULL, 0, "%s", "accelerometer"); |
| if (unlikely(IS_ERR(accelerometer_dev))) { |
| ret = PTR_ERR(accelerometer_dev); |
| accelerometer_dev = NULL; |
| goto err_create_accelerometer_device; |
| } |
| |
| |
| ret = device_create_file(accelerometer_dev, &dev_attr_PhoneOnOffFlag); |
| if (ret) |
| goto err_create_accelerometer_device_file; |
| |
| |
| ret = device_create_file(accelerometer_dev, &dev_attr_debug_en); |
| if (ret) |
| goto err_create_accelerometer_debug_en_device_file; |
| |
| return 0; |
| |
| err_create_accelerometer_debug_en_device_file: |
| device_remove_file(accelerometer_dev, &dev_attr_PhoneOnOffFlag); |
| err_create_accelerometer_device_file: |
| device_unregister(accelerometer_dev); |
| err_create_accelerometer_device: |
| class_destroy(htc_accelerometer_class); |
| err_create_class: |
| |
| return ret; |
| } |
| |
| static const struct file_operations bma_fops = { |
| .owner = THIS_MODULE, |
| .open = bma_open, |
| .release = bma_release, |
| |
| #if HAVE_COMPAT_IOCTL |
| .compat_ioctl = bma_ioctl, |
| #endif |
| #if HAVE_UNLOCKED_IOCTL |
| .unlocked_ioctl = bma_ioctl, |
| #endif |
| }; |
| |
| static struct miscdevice bma_device = { |
| .minor = MISC_DYNAMIC_MINOR, |
| .name = "bma150", |
| .fops = &bma_fops, |
| }; |
| |
| int bma250_probe(struct i2c_client *client, const struct i2c_device_id *id) |
| { |
| struct bma250_data *bma; |
| char buffer[2]; |
| int err = 0; |
| |
| if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) { |
| err = -ENODEV; |
| goto exit_check_functionality_failed; |
| } |
| |
| bma = kzalloc(sizeof(struct bma250_data), GFP_KERNEL); |
| if (!bma) { |
| err = -ENOMEM; |
| goto exit_alloc_data_failed; |
| } |
| |
| i2c_set_clientdata(client, bma); |
| |
| pdata = client->dev.platform_data; |
| if (pdata == NULL) { |
| E("bma250_init_client: platform data is NULL\n"); |
| goto exit_platform_data_null; |
| } |
| |
| pdata->gs_kvalue = gs_kvalue; |
| printk(KERN_INFO "BMA250 G-sensor I2C driver: gs_kvalue = 0x%X\n", |
| pdata->gs_kvalue); |
| |
| this_client = client; |
| |
| buffer[0] = bma250_CHIP_ID_REG; |
| err = BMA_I2C_RxData(buffer, 1); |
| if (err < 0) |
| goto exit_wrong_ID; |
| D("%s: CHIP ID = 0x%02x\n", __func__, buffer[0]); |
| if ((buffer[0] != 0x3) && (buffer[0] != 0xF9)) { |
| E("Wrong chip ID of BMA250 or BMA250E!!\n"); |
| goto exit_wrong_ID; |
| } |
| |
| err = BMA_Init(); |
| if (err < 0) { |
| E("bma250_probe: bma_init failed\n"); |
| goto exit_init_failed; |
| } |
| |
| err = misc_register(&bma_device); |
| if (err) { |
| E("bma250_probe: device register failed\n"); |
| goto exit_misc_device_register_failed; |
| } |
| |
| #ifdef EARLY_SUSPEND_BMA |
| bma->early_suspend.suspend = bma250_early_suspend; |
| bma->early_suspend.resume = bma250_late_resume; |
| register_early_suspend(&bma->early_suspend); |
| #endif |
| |
| err = bma250_registerAttr(); |
| if (err) { |
| E("%s: set spi_bma150_registerAttr fail!\n", __func__); |
| goto err_registerAttr; |
| } |
| D("%s: I2C retry 10 times version. OK\n", __func__); |
| debug_flag = 0; |
| |
| return 0; |
| |
| err_registerAttr: |
| exit_misc_device_register_failed: |
| exit_init_failed: |
| exit_wrong_ID: |
| exit_platform_data_null: |
| kfree(bma); |
| exit_alloc_data_failed: |
| exit_check_functionality_failed: |
| return err; |
| } |
| |
| static int bma250_remove(struct i2c_client *client) |
| { |
| struct bma250_data *bma = i2c_get_clientdata(client); |
| kfree(bma); |
| return 0; |
| } |
| |
| static const struct i2c_device_id bma250_id[] = { |
| { BMA250_I2C_NAME, 0 }, |
| { } |
| }; |
| |
| static struct i2c_driver bma250_driver = { |
| .probe = bma250_probe, |
| .remove = bma250_remove, |
| .id_table = bma250_id, |
| |
| #ifndef EARLY_SUSPEND_BMA |
| .suspend = bma250_suspend, |
| .resume = bma250_resume, |
| #endif |
| .driver = { |
| .name = BMA250_I2C_NAME, |
| }, |
| }; |
| |
| static int __init bma250_init(void) |
| { |
| printk(KERN_INFO "BMA250 G-sensor driver: init\n"); |
| return i2c_add_driver(&bma250_driver); |
| } |
| |
| static void __exit bma250_exit(void) |
| { |
| i2c_del_driver(&bma250_driver); |
| } |
| |
| module_init(bma250_init); |
| module_exit(bma250_exit); |
| |
| MODULE_DESCRIPTION("BMA250 G-sensor driver"); |
| MODULE_LICENSE("GPL"); |
| |