/* * bma180.c * BMA-180 Accelerometer driver * * Copyright (C) 2010 Texas Instruments * Author: Dan Murphy * * 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, see . * * Derived work from bma180_accl.c from Jorge Bustamante */ #include #include #include #include #include #include #include #include #include #define BMA180_DEBUG 1 #define DEVICE_NAME "bma180" #define DRIVER_NAME "bma180_accel" #define BMA180_OFFSET_Z 0x3A #define BMA180_OFFSET_Y 0x39 #define BMA180_OFFSET_X 0x38 #define BMA180_OFFSET_T 0x37 #define BMA180_OFFSET_LSB2 0x36 #define BMA180_OFFSET_LSB1 0x35 #define BMA180_GAIN_Z 0x34 #define BMA180_GAIN_Y 0x33 #define BMA180_GAIN_X 0x32 #define BMA180_GAIN_T 0x31 #define BMA180_TCO_Z 0x30 #define BMA180_TCO_Y 0x2F #define BMA180_TCO_X 0x2E #define BMA180_CD2 0x2D #define BMA180_CD1 0x2C #define BMA180_SLOPE_TH 0x2B #define BMA180_HIGH_TH 0x2A #define BMA180_LOW_TH 0x29 #define BMA180_TAPSENS_TH 0x28 #define BMA180_HIGH_DUR 0x27 #define BMA180_LOW_DUR 0x26 #define BMA180_HIGH_LOW_INFO 0x25 #define BMA180_SLOPE_TAPSENS_INFO 0x24 #define BMA180_HY 0x23 #define BMA180_CTRL_REG4 0x22 #define BMA180_CTRL_REG3 0x21 #define BMA180_BW_TCS 0x20 #define BMA180_RESET 0x10 #define BMA180_CTRL_REG2 0x0F #define BMA180_CTRL_REG1 0x0E #define BMA180_CTRL_REG0 0x0D #define BMA180_STATUS_REG4 0x0C #define BMA180_STATUS_REG3 0x0B #define BMA180_STATUS_REG2 0x0A #define BMA180_STATUS_REG1 0x09 #define BMA180_TEMP 0x08 #define BMA180_ACC_Z_MSB 0x07 #define BMA180_ACC_Z_LSB 0x06 #define BMA180_ACC_Y_MSB 0x05 #define BMA180_ACC_Y_LSB 0x04 #define BMA180_ACC_X_MSB 0x03 #define BMA180_ACC_X_LSB 0x02 #define BMA180_VERSION 0x01 #define BMA180_CHIP_ID 0x00 #define SLEEP 0x00 #define WAKEUP 0x01 struct bma180_accel_data { struct bma180accel_platform_data *pdata; struct i2c_client *client; struct input_dev *input_dev; struct delayed_work wq; struct mutex mutex; uint32_t def_poll_rate; int wakeup_flag; }; static uint32_t accl_debug; module_param_named(bma180_debug, accl_debug, uint, 0664); static int g_range_table[7] = { 1000, 1500, 2000, 3000, 4000, 8000, 16000, }; /* interval between samples for the different rates, in msecs */ static const unsigned int bma180_measure_interval[] = { 1000 / 10, 1000 / 20, 1000 / 40, 1000 / 75, 1000 / 150, 1000 / 300, 1000 / 600, 1000 / 1200 }; #ifdef BMA180_DEBUG struct bma180_reg { const char *name; uint8_t reg; int writeable; } bma180_regs[] = { { "CHIP_ID", BMA180_CHIP_ID, 0 }, { "VERSION", BMA180_VERSION, 0 }, { "X_LSB", BMA180_ACC_X_LSB, 0 }, { "X_MSB", BMA180_ACC_X_MSB, 0 }, { "Y_LSB", BMA180_ACC_Y_LSB, 0 }, { "Y_MSB", BMA180_ACC_Y_MSB, 0 }, { "Z_LSB", BMA180_ACC_Z_LSB, 0 }, { "Z_MSB", BMA180_ACC_Z_MSB, 0 }, { "TEMP", BMA180_TEMP, 0 }, { "STATUS1", BMA180_STATUS_REG1, 0 }, { "STATUS2", BMA180_STATUS_REG2, 0 }, { "STATUS3", BMA180_STATUS_REG3, 0 }, { "STATUS4", BMA180_STATUS_REG4, 0 }, { "CTRL0", BMA180_CTRL_REG0, 1 }, { "CTRL1", BMA180_CTRL_REG1, 1 }, { "CTRL2", BMA180_CTRL_REG2, 1 }, { "RESET", BMA180_RESET, 1 }, { "BW_TCS", BMA180_BW_TCS, 1 }, { "CTRL3", BMA180_CTRL_REG3, 1 }, { "CTRL4", BMA180_CTRL_REG4, 1 }, { "HY", BMA180_HY, 1 }, { "TAP_INFO", BMA180_SLOPE_TAPSENS_INFO, 1 }, { "HI_LOW_INFO", BMA180_HIGH_LOW_INFO, 1 }, { "LOW_DUR", BMA180_LOW_DUR, 1 }, { "HIGH_DUR", BMA180_HIGH_DUR, 1 }, { "TAP_THRESH", BMA180_TAPSENS_TH, 1 }, { "LOW_THRESH", BMA180_LOW_TH, 1 }, { "HIGH_THRESH", BMA180_HIGH_TH, 1 }, { "SLOPE_THRESH", BMA180_SLOPE_TH, 1 }, { "CD1", BMA180_CD1, 1 }, { "CD2", BMA180_CD2, 1 }, { "TCO_X", BMA180_TCO_X, 1 }, { "TCO_Y", BMA180_TCO_Y, 1 }, { "TCO_Z", BMA180_TCO_Z, 1 }, { "GAIN_T", BMA180_GAIN_T, 1 }, { "GAIN_X", BMA180_GAIN_X, 1 }, { "GAIN_Y", BMA180_GAIN_Y, 1 }, { "GAIN_Z", BMA180_GAIN_Z, 1 }, { "OFFSET_LSB1", BMA180_OFFSET_LSB1, 1 }, { "OFFSET_LSB2", BMA180_OFFSET_LSB2, 1 }, { "OFFSET_T", BMA180_OFFSET_T, 1 }, { "OFFSET_X", BMA180_OFFSET_X, 1 }, { "OFFSET_Y", BMA180_OFFSET_Y, 1 }, { "OFFSET_Z", BMA180_OFFSET_Z, 1 }, }; #endif static int bma180_write(struct bma180_accel_data *data, u8 reg, u8 val) { int ret = 0; mutex_lock(&data->mutex); ret = i2c_smbus_write_byte_data(data->client, reg, val); if (ret < 0) dev_err(&data->client->dev, "i2c_smbus_write_byte_data failed\n"); mutex_unlock(&data->mutex); return ret; } static int bma180_read_transfer(struct bma180_accel_data *data, unsigned short data_addr, char *data_buf, int count) { int ret; int counter = 5; char *data_buffer = data_buf; struct i2c_msg msgs[] = { { .addr = data->client->addr, .flags = data->client->flags & I2C_M_TEN, .len = 1, .buf = data_buffer, }, { .addr = data->client->addr, .flags = (data->client->flags & I2C_M_TEN) | I2C_M_RD, .len = count, .buf = data_buffer, }, }; data_buffer[0] = data_addr; msgs->buf = data_buffer; do { ret = i2c_transfer(data->client->adapter, msgs, 2); if (ret != 2) { dev_err(&data->client->dev, "i2c_transfer failed\n"); counter--; msleep(1); } else { return 0; } } while (counter >= 0); return -1; } static int bma180_accel_device_hw_set_bandwidth(struct bma180_accel_data *data, int bandwidth) { uint8_t reg_val; uint8_t int_val; bma180_read_transfer(data, BMA180_CTRL_REG3, &int_val, 1); bma180_write(data, BMA180_CTRL_REG3, 0x00); bma180_read_transfer(data, BMA180_BW_TCS, ®_val, 1); reg_val = (reg_val & 0x0F) | ((bandwidth << 4) & 0xF0); bma180_write(data, BMA180_BW_TCS, reg_val); msleep(10); bma180_write(data, BMA180_CTRL_REG3, int_val); return 0; } static void bma180_device_sleep_wakeup(struct bma180_accel_data *data, int do_wakeup) { int ret; uint8_t reg_val; bma180_read_transfer(data, BMA180_CTRL_REG0, ®_val, 1); if (do_wakeup) { reg_val &= ~BMA180_SLEEP; ret = bma180_write(data, BMA180_CTRL_REG0, reg_val); if (ret < 0) dev_err(&data->client->dev, "can't wakeup device\n"); msleep(10); } else { reg_val |= BMA180_SLEEP; ret = bma180_write(data, BMA180_CTRL_REG0, reg_val); if (ret < 0) dev_err(&data->client->dev, "sleep mode can't be set\n"); } } static irqreturn_t bma180_accel_thread_irq(int irq, void *dev_data) { struct bma180_accel_data *data = (struct bma180_accel_data *) dev_data; if (!data->client->irq) schedule_delayed_work(&data->wq, 0); return IRQ_HANDLED; } static int bma180_accel_data_ready(struct bma180_accel_data *data) { int ret; uint8_t data_val_h, data_val_l; short int x = 0; short int y = 0; short int z = 0; uint8_t data_buffer[6]; ret = bma180_read_transfer(data, BMA180_ACC_X_LSB, data_buffer, 6); if (ret != 0) { dev_err(&data->client->dev, "bma180_read_data_ready failed\n"); return -1; } data_val_l = data_buffer[0]; data_val_h = data_buffer[1]; if (accl_debug) pr_info("%s: X low 0x%X X high 0x%X\n", __func__, data_val_l, data_val_h); x = ((data_val_h << 8) | data_val_l); x = (x >> 2); x = x * g_range_table[data->pdata->g_range]/data->pdata->bit_mode; data_val_l = data_buffer[2]; data_val_h = data_buffer[3]; if (accl_debug) pr_info("%s: Y low 0x%X Y high 0x%X\n", __func__, data_val_l, data_val_h); y = ((data_val_h << 8) | data_val_l); y = (y >> 2); y = y * g_range_table[data->pdata->g_range]/data->pdata->bit_mode; data_val_l = data_buffer[4]; data_val_h = data_buffer[5]; if (accl_debug) pr_info("%s: Z low 0x%X Z high 0x%X\n", __func__, data_val_l, data_val_h); z = ((data_val_h << 8) | data_val_l); z = (z >> 2); z = z * g_range_table[data->pdata->g_range]/data->pdata->bit_mode; if (accl_debug) pr_info("%s: X: 0x%X Y: 0x%X Z: 0x%X\n", __func__, x, y, z); input_report_abs(data->input_dev, ABS_X, x); input_report_abs(data->input_dev, ABS_Y, y); input_report_abs(data->input_dev, ABS_Z, z); input_sync(data->input_dev); return 0; } static void bma180_accel_device_worklogic(struct work_struct *work) { struct bma180_accel_data *data = container_of((struct delayed_work *)work, struct bma180_accel_data, wq); bma180_accel_data_ready(data); if (!data->client->irq) schedule_delayed_work(&data->wq, msecs_to_jiffies(data->def_poll_rate)); } static ssize_t bma180_show_attr_enable(struct device *dev, struct device_attribute *attr, char *buf) { struct platform_device *pdev = to_platform_device(dev); struct bma180_accel_data *data = platform_get_drvdata(pdev); return sprintf(buf, "%d\n", data->pdata->mode); } static ssize_t bma180_store_attr_enable(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) { struct platform_device *pdev = to_platform_device(dev); struct bma180_accel_data *data = platform_get_drvdata(pdev); unsigned long val; int error, enable; error = strict_strtoul(buf, 0, &val); if (error) return error; enable = !!val; mutex_lock(&data->mutex); if (data->pdata->mode == enable) { mutex_unlock(&data->mutex); return count; } data->pdata->mode = enable; mutex_unlock(&data->mutex); if (enable) { data->wakeup_flag = 0; bma180_device_sleep_wakeup(data, WAKEUP); schedule_delayed_work(&data->wq, 0); } else { cancel_delayed_work_sync(&data->wq); bma180_device_sleep_wakeup(data, SLEEP); } data->pdata->mode = enable; return count; } static ssize_t bma180_show_attr_delay(struct device *dev, struct device_attribute *attr, char *buf) { struct platform_device *pdev = to_platform_device(dev); struct bma180_accel_data *data = platform_get_drvdata(pdev); return sprintf(buf, "%d\n", data->def_poll_rate); } static ssize_t bma180_store_attr_delay(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) { struct platform_device *pdev = to_platform_device(dev); struct bma180_accel_data *data = platform_get_drvdata(pdev); unsigned long interval; int error; int i = 0; error = strict_strtoul(buf, 0, &interval); if (error) return error; if (interval < 0) return -EINVAL; cancel_delayed_work_sync(&data->wq); if (interval >= bma180_measure_interval[BMA_BW_10HZ]) i = BMA_BW_10HZ; else if (interval >= bma180_measure_interval[BMA_BW_20HZ]) i = BMA_BW_20HZ; else if (interval >= bma180_measure_interval[BMA_BW_40HZ]) i = BMA_BW_40HZ; else if (interval >= bma180_measure_interval[BMA_BW_75HZ]) i = BMA_BW_75HZ; else if (interval >= bma180_measure_interval[BMA_BW_150HZ]) i = BMA_BW_150HZ; else if (interval >= bma180_measure_interval[BMA_BW_300HZ]) i = BMA_BW_300HZ; else if (interval >= bma180_measure_interval[BMA_BW_600HZ]) i = BMA_BW_600HZ; else i = BMA_BW_1200HZ; data->def_poll_rate = interval; bma180_accel_device_hw_set_bandwidth(data, i); schedule_delayed_work(&data->wq, 0); return count; } #ifdef BMA180_DEBUG static ssize_t bma180_registers_show(struct device *dev, struct device_attribute *attr, char *buf) { struct platform_device *pdev = to_platform_device(dev); struct bma180_accel_data *data = platform_get_drvdata(pdev); unsigned i, n, reg_count; uint8_t value; reg_count = sizeof(bma180_regs) / sizeof(bma180_regs[0]); for (i = 0, n = 0; i < reg_count; i++) { bma180_read_transfer(data, bma180_regs[i].reg, &value, 1); n += scnprintf(buf + n, PAGE_SIZE - n, "%-20s = 0x%02X\n", bma180_regs[i].name, value); } return n; } static ssize_t bma180_registers_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) { struct platform_device *pdev = to_platform_device(dev); struct bma180_accel_data *data = platform_get_drvdata(pdev); unsigned i, reg_count, value; int error = 0; char name[30]; if (count >= 30) { pr_err("%s:input too long\n", __func__); return -1; } if (sscanf(buf, "%s %x", name, &value) != 2) { pr_err("%s:unable to parse input\n", __func__); return -1; } reg_count = sizeof(bma180_regs) / sizeof(bma180_regs[0]); for (i = 0; i < reg_count; i++) { if (!strcmp(name, bma180_regs[i].name)) { if (bma180_regs[i].writeable) { error = bma180_write(data, bma180_regs[i].reg, value); if (error) { pr_err("%s:Failed to write %s\n", __func__, name); return -1; } } else { pr_err("%s:Register %s is not writeable\n", __func__, name); return -1; } return count; } } pr_err("%s:no such register %s\n", __func__, name); return -1; } static DEVICE_ATTR(registers, S_IWUSR | S_IRUGO, bma180_registers_show, bma180_registers_store); #endif static DEVICE_ATTR(enable, S_IWUSR | S_IRUGO, bma180_show_attr_enable, bma180_store_attr_enable); static DEVICE_ATTR(delay, S_IWUSR | S_IRUGO, bma180_show_attr_delay, bma180_store_attr_delay); static struct attribute *bma180_accel_attrs[] = { &dev_attr_enable.attr, &dev_attr_delay.attr, #ifdef BMA180_DEBUG &dev_attr_registers.attr, #endif NULL }; static const struct attribute_group bma180_accel_attr_group = { .attrs = bma180_accel_attrs, }; static int bma180_accel_device_hw_reset(struct bma180_accel_data *data) { /* write 0xB6 to this register to do a soft-reset */ bma180_write(data, BMA180_RESET, 0xB6); return 0; } static int bma180_accel_device_hw_reset_int(struct bma180_accel_data *data) { uint8_t reg_val; bma180_read_transfer(data, BMA180_CTRL_REG0, ®_val, 1); reg_val |= 0x40; bma180_write(data, BMA180_CTRL_REG0, reg_val); return 0; } static int bma180_accel_device_hw_set_grange(struct bma180_accel_data *data, int grange) { uint8_t reg_val; bma180_read_transfer(data, BMA180_OFFSET_LSB1, ®_val, 1); reg_val = (reg_val & 0xF1) | ((grange << 1) & 0x0E); bma180_write(data, BMA180_OFFSET_LSB1, reg_val); return 0; } static int bma180_accel_device_hw_set_smp_skip(struct bma180_accel_data *data, int smp_skip) { uint8_t reg_val; bma180_read_transfer(data, BMA180_OFFSET_LSB1, ®_val, 1); reg_val = (reg_val & 0xFE) | ((smp_skip << 0) & 0x01); bma180_write(data, BMA180_OFFSET_LSB1, reg_val); return 0; } static int bma180_accel_device_hw_set_mode(struct bma180_accel_data *data, int mode) { uint8_t reg_val; bma180_read_transfer(data, BMA180_TCO_Z, ®_val, 1); reg_val = (reg_val & 0xFC) | ((mode << 0) & 0x03); bma180_write(data, BMA180_TCO_Z, reg_val); return 0; } static int bma180_accel_device_hw_set_12bits(struct bma180_accel_data *data, int mode) { uint8_t reg_val; bma180_read_transfer(data, BMA180_OFFSET_T, ®_val, 1); reg_val = (reg_val & 0xFE) | ((mode << 0) & 0x01); bma180_write(data, BMA180_OFFSET_T, reg_val); return 0; } static int bma180_accel_device_hw_init(struct bma180_accel_data *data) { int ret = 0; bma180_accel_device_hw_reset(data); msleep(1); bma180_write(data, BMA180_CTRL_REG0, data->pdata->ctrl_reg0); bma180_write(data, BMA180_CTRL_REG3, 0x00); bma180_write(data, BMA180_HIGH_LOW_INFO, 0x00); bma180_write(data, BMA180_SLOPE_TAPSENS_INFO, 0x00); bma180_write(data, BMA180_GAIN_Y, 0xA9); bma180_write(data, BMA180_HIGH_DUR, 0x00); bma180_accel_device_hw_set_grange(data, data->pdata->g_range); bma180_accel_device_hw_set_bandwidth(data, data->pdata->bandwidth); bma180_accel_device_hw_set_mode(data, data->pdata->mode); bma180_accel_device_hw_set_smp_skip(data, data->pdata->smp_skip); bma180_accel_device_hw_set_12bits(data, data->pdata->bit_mode); bma180_write(data, BMA180_CTRL_REG3, 0x02); bma180_accel_device_hw_reset_int(data); return ret; } static int __devinit bma180_accel_driver_probe(struct i2c_client *client, const struct i2c_device_id *id) { struct bma180accel_platform_data *pdata = client->dev.platform_data; struct bma180_accel_data *data; int ret = 0; pr_info("%s: Enter\n", __func__); if (pdata == NULL) { pr_err("%s: Platform data not found\n", __func__); return -ENODEV; } if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) { pr_err("%s: need I2C_FUNC_I2C\n", __func__); return -ENODEV; } /* alloc memory for data structure */ data = kzalloc(sizeof(struct bma180_accel_data), GFP_KERNEL); if (!data) { ret = -ENOMEM; goto error; } data->pdata = pdata; data->client = client; data->def_poll_rate = pdata->def_poll_rate; i2c_set_clientdata(client, data); data->input_dev = input_allocate_device(); if (data->input_dev == NULL) { ret = -ENOMEM; dev_err(&data->client->dev, "Failed to allocate input device\n"); goto error; } INIT_DELAYED_WORK(&data->wq, bma180_accel_device_worklogic); mutex_init(&data->mutex); data->input_dev->name = "bma180"; data->input_dev->id.bustype = BUS_I2C; __set_bit(EV_ABS, data->input_dev->evbit); input_set_abs_params(data->input_dev, ABS_X, -g_range_table[data->pdata->g_range], g_range_table[data->pdata->g_range], data->pdata->fuzz_x, 0); input_set_abs_params(data->input_dev, ABS_Y, -g_range_table[data->pdata->g_range], g_range_table[data->pdata->g_range], data->pdata->fuzz_y, 0); input_set_abs_params(data->input_dev, ABS_Z, -g_range_table[data->pdata->g_range], g_range_table[data->pdata->g_range], data->pdata->fuzz_z, 0); data->input_dev->dev.parent = &data->client->dev; input_set_drvdata(data->input_dev, data); ret = input_register_device(data->input_dev); if (ret) { dev_err(&data->client->dev, "Unable to register input device\n"); } if (data->client->irq) { ret = request_threaded_irq(data->client->irq, NULL, bma180_accel_thread_irq, IRQF_TRIGGER_RISING | IRQF_ONESHOT, data->client->name, data); if (ret < 0) { dev_err(&data->client->dev, "request_threaded_irq failed\n"); goto error_1; } } ret = bma180_accel_device_hw_init(data); if (ret) goto error_1; ret = sysfs_create_group(&client->dev.kobj, &bma180_accel_attr_group); if (ret) goto error_1; return 0; error_1: input_free_device(data->input_dev); mutex_destroy(&data->mutex); kfree(data); error: return ret; } static int __devexit bma180_accel_driver_remove(struct i2c_client *client) { struct bma180_accel_data *data = i2c_get_clientdata(client); int ret = 0; sysfs_remove_group(&client->dev.kobj, &bma180_accel_attr_group); if (data->client->irq) free_irq(data->client->irq, data); cancel_delayed_work_sync(&data->wq); if (data->input_dev) input_free_device(data->input_dev); i2c_set_clientdata(client, NULL); kfree(data); return ret; } #ifdef CONFIG_PM static int bma180_accel_driver_suspend(struct i2c_client *client, pm_message_t mesg) { struct bma180_accel_data *data = i2c_get_clientdata(client); mutex_lock(&data->mutex); if (!data->pdata->mode) { mutex_unlock(&data->mutex); return 0; } data->pdata->mode = 0; mutex_unlock(&data->mutex); data->wakeup_flag = 1; cancel_delayed_work_sync(&data->wq); bma180_device_sleep_wakeup(data, SLEEP); return 0; } static int bma180_accel_driver_resume(struct i2c_client *client) { struct bma180_accel_data *data = i2c_get_clientdata(client); mutex_lock(&data->mutex); if (data->pdata->mode) { mutex_unlock(&data->mutex); return 0; } if (!data->wakeup_flag) { mutex_unlock(&data->mutex); return 0; } data->pdata->mode = 1; mutex_unlock(&data->mutex); data->wakeup_flag = 0; bma180_device_sleep_wakeup(data, WAKEUP); schedule_delayed_work(&data->wq, 0); return 0; } #endif static const struct i2c_device_id bma180_accel_idtable[] = { { DRIVER_NAME, 0 }, { }, }; MODULE_DEVICE_TABLE(i2c, bma180_accel_idtable); static struct i2c_driver bma180_accel_driver = { .probe = bma180_accel_driver_probe, .remove = bma180_accel_driver_remove, .id_table = bma180_accel_idtable, #ifdef CONFIG_PM .suspend = bma180_accel_driver_suspend, .resume = bma180_accel_driver_resume, #endif .driver = { .name = DRIVER_NAME }, }; static int __init bma180_accel_driver_init(void) { return i2c_add_driver(&bma180_accel_driver); } static void __exit bma180_accel_driver_exit(void) { i2c_del_driver(&bma180_accel_driver); } module_init(bma180_accel_driver_init); module_exit(bma180_accel_driver_exit); MODULE_DESCRIPTION("BMA-180 Accelerometer Driver"); MODULE_LICENSE("GPL"); MODULE_AUTHOR("Dan Murphy ");