From patchwork Thu Jul 15 08:58:42 2010 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Donggeun Kim X-Patchwork-Id: 112207 Received: from vger.kernel.org (vger.kernel.org [209.132.180.67]) by demeter.kernel.org (8.14.4/8.14.3) with ESMTP id o6F8woAX030090 for ; Thu, 15 Jul 2010 08:58:50 GMT Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S932678Ab0GOI6t (ORCPT ); Thu, 15 Jul 2010 04:58:49 -0400 Received: from mailout1.samsung.com ([203.254.224.24]:46124 "EHLO mailout1.samsung.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S932652Ab0GOI6r (ORCPT ); Thu, 15 Jul 2010 04:58:47 -0400 Received: from epmmp2 (mailout1.samsung.com [203.254.224.24]) by mailout1.samsung.com (Sun Java(tm) System Messaging Server 7u3-15.01 64bit (built Feb 12 2010)) with ESMTP id <0L5L004BDCXW9150@mailout1.samsung.com> for linux-input@vger.kernel.org; Thu, 15 Jul 2010 17:58:44 +0900 (KST) Received: from TNRNDGASPAPP1.tn.corp.samsungelectronics.net ([165.213.149.150]) by mmp2.samsung.com (iPlanet Messaging Server 5.2 Patch 2 (built Jul 14 2004)) with ESMTPA id <0L5L000D1CXWAV@mmp2.samsung.com> for linux-input@vger.kernel.org; Thu, 15 Jul 2010 17:58:44 +0900 (KST) Received: from localhost.localdomain ([10.89.53.115]) by TNRNDGASPAPP1.tn.corp.samsungelectronics.net with Microsoft SMTPSVC(6.0.3790.4675); Thu, 15 Jul 2010 17:58:44 +0900 Date: Thu, 15 Jul 2010 17:58:42 +0900 From: Donggeun Kim Subject: [PATCH v2] input: smb380 accelerometer driver In-reply-to: <1279115698.2926.94.camel@lovely.krouter> To: linux-input@vger.kernel.org Cc: dmitry.torokhov@gmail.com, kyungmin.park@samsung.com Message-id: <1279184322-2043-1-git-send-email-dg77.kim@samsung.com> X-Mailer: git-send-email 1.6.3.3 Content-transfer-encoding: 7BIT References: <1279115698.2926.94.camel@lovely.krouter> X-OriginalArrivalTime: 15 Jul 2010 08:58:44.0314 (UTC) FILETIME=[EDC623A0:01CB23FB] Sender: linux-input-owner@vger.kernel.org Precedence: bulk List-ID: X-Mailing-List: linux-input@vger.kernel.org X-Greylist: IP, sender and recipient auto-whitelisted, not delayed by milter-greylist-4.2.3 (demeter.kernel.org [140.211.167.41]); Thu, 15 Jul 2010 08:58:50 +0000 (UTC) diff --git a/drivers/input/misc/Kconfig b/drivers/input/misc/Kconfig index c44b9ea..ea9cfae 100644 --- a/drivers/input/misc/Kconfig +++ b/drivers/input/misc/Kconfig @@ -390,4 +390,14 @@ config INPUT_PCAP To compile this driver as a module, choose M here: the module will be called pcap_keys. +config INPUT_SMB380 + tristate "SMB380/BMA023 Triaxial acceleration sensor" + depends on I2C + help + Say Y here if you want to support Bosch SMB380/BMA023 + connected via an I2C bus. + + To compile this driver as a module, choose M here: the + module will be called smb380. + endif diff --git a/drivers/input/misc/Makefile b/drivers/input/misc/Makefile index 71fe57d..4633b69 100644 --- a/drivers/input/misc/Makefile +++ b/drivers/input/misc/Makefile @@ -29,6 +29,7 @@ obj-$(CONFIG_INPUT_POWERMATE) += powermate.o obj-$(CONFIG_INPUT_RB532_BUTTON) += rb532_button.o obj-$(CONFIG_INPUT_GPIO_ROTARY_ENCODER) += rotary_encoder.o obj-$(CONFIG_INPUT_SGI_BTNS) += sgi_btns.o +obj-$(CONFIG_INPUT_SMB380) += smb380.o obj-$(CONFIG_INPUT_SPARCSPKR) += sparcspkr.o obj-$(CONFIG_INPUT_TWL4030_PWRBUTTON) += twl4030-pwrbutton.o obj-$(CONFIG_INPUT_TWL4030_VIBRA) += twl4030-vibra.o diff --git a/drivers/input/misc/smb380.c b/drivers/input/misc/smb380.c new file mode 100644 index 0000000..b13fe1c --- /dev/null +++ b/drivers/input/misc/smb380.c @@ -0,0 +1,719 @@ +/* + * smb380.c - SMB380 Tri-axis accelerometer driver + * + * Copyright (C) 2010 Samsung Eletronics Co.Ltd + * Kim Kyuwon + * Kyungmin Park + * Donggeun Kim + * + * 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. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define SMB380_CHIP_ID_REG 0x00 +#define SMB380_X_LSB_REG 0x02 +#define SMB380_X_MSB_REG 0x03 +#define SMB380_Y_LSB_REG 0x04 +#define SMB380_Y_MSB_REG 0x05 +#define SMB380_Z_LSB_REG 0x06 +#define SMB380_Z_MSB_REG 0x07 +#define SMB380_TEMP_REG 0x08 +#define SMB380_CTRL1_REG 0x0a +#define SMB380_CTRL2_REG 0x0b +#define SMB380_SETTINGS1_REG 0x0c +#define SMB380_SETTINGS2_REG 0x0d +#define SMB380_SETTINGS3_REG 0x0e +#define SMB380_SETTINGS4_REG 0x0f +#define SMB380_SETTINGS5_REG 0x10 +#define SMB380_SETTINGS6_REG 0x11 +#define SMB380_RANGE_BW_REG 0x14 +#define SMB380_CONF2_REG 0x15 + +#define SMB380_CHIP_ID 0x2 + +#define SMB380_NEW_DATA_INT_SHIFT 5 +#define SMB380_NEW_DATA_INT_MASK (0x1 << 5) + +#define SMB380_RANGE_SHIFT 3 +#define SMB380_RANGE_MASK (0x3 << 3) +#define SMB380_BANDWIDTH_SHIFT 0 +#define SMB380_BANDWIDTH_MASK (0x7) + +#define SMB380_HG_HYST_SHIFT 3 +#define SMB380_HG_HYST_MASK (0x7 << 3) +#define SMB380_LG_HYST_SHIFT 0 +#define SMB380_LG_HYST_MASK (0x7) + +#define SMB380_HG_DUR_SHIFT (0x0) +#define SMB380_HG_DUR_MASK (0xff) +#define SMB380_HG_THRES_SHIFT (0x0) +#define SMB380_HG_THRES_MASK (0xff) +#define SMB380_LG_DUR_SHIFT (0x0) +#define SMB380_LG_DUR_MASK (0xff) +#define SMB380_LG_THRES_SHIFT (0x0) +#define SMB380_LG_THRES_MASK (0xff) + +#define SMB380_ENABLE_HG_SHIFT 1 +#define SMB380_ENABLE_HG_MASK (0x1 << 1) +#define SMB380_ENABLE_LG_SHIFT 0 +#define SMB380_ENABLE_LG_MASK (0x1) + +#define SMB380_SLEEP_SHIFT 0 +#define SMB380_SLEEP_MASK (0x1) + +#define SMB380_ACCEL_BITS 10 +#define SMB380_MAX_VALUE ((1 << ((SMB380_ACCEL_BITS) - 1)) - 1) +#define SMB380_MIN_VALUE (-(1 << ((SMB380_ACCEL_BITS) - 1))) + +#define SMB380_DEFAULT_RANGE RANGE_2G +#define SMB380_DEFAULT_BANDWIDTH BW_25HZ +#define SMB380_DEFAULT_NEW_DATA_INT 0 +#define SMB380_DEFAULT_HG_INT 1 +#define SMB380_DEFAULT_LG_INT 1 +#define SMB380_DEFAULT_HG_DURATION 0x96 +#define SMB380_DEFAULT_HG_THRESHOLD 0xa0 +#define SMB380_DEFAULT_HG_HYST 0 +#define SMB380_DEFAULT_LG_DURATION 0x96 +#define SMB380_DEFAULT_LG_THRESHOLD 0x14 +#define SMB380_DEFAULT_LG_HYST 0 + +struct smb380_data { + s16 x; + s16 y; + s16 z; + u8 temp; +}; + +struct smb380_sensor { + struct i2c_client *client; + struct device *dev; + struct input_dev *idev; + struct work_struct work; + struct mutex lock; + + struct smb380_data data; + enum scale_range range; + enum filter_bw bandwidth; + u8 new_data_int; + u8 hg_int; + u8 lg_int; + u8 lg_dur; + u8 lg_thres; + u8 lg_hyst; + u8 hg_dur; + u8 hg_thres; + u8 hg_hyst; +}; + +static int smb380_write_reg(struct i2c_client *client, u8 reg, u8 val) +{ + int ret; + + /* + * According to the datasheet, the interrupt should be deactivated + * on the host side when write sequences operate + */ + disable_irq_nosync(client->irq); + ret = i2c_smbus_write_byte_data(client, reg, val); + enable_irq(client->irq); + + if (ret < 0) + dev_err(&client->dev, "%s: reg 0x%x, val 0x%x, err %d\n", + __func__, reg, val, ret); + return ret; +} + +static int smb380_read_reg(struct i2c_client *client, u8 reg) +{ + int ret = i2c_smbus_read_byte_data(client, reg); + + if (ret < 0) + dev_err(&client->dev, "%s: reg 0x%x, err %d\n", + __func__, reg, ret); + return ret; +} + +static int smb380_xyz_read_reg(struct i2c_client *client, + u8 *buffer, int length) +{ + struct i2c_msg msg[] = { + { + .addr = client->addr, + .flags = 0, + .len = 1, + .buf = buffer, + }, { + .addr = client->addr, + .flags = I2C_M_RD, + .len = length, + .buf = buffer, + }, + }; + return i2c_transfer(client->adapter, msg, 2); +} + +static int smb380_set_reg_bits(struct i2c_client *client, + int val, int shift, u8 mask, u8 reg) +{ + u8 data = smb380_read_reg(client, reg); + + data = (data & ~mask) | ((val << shift) & mask); + return smb380_write_reg(client, reg, data); +} + +static u8 smb380_get_reg_bits(struct i2c_client *client, int shift, + u8 mask, u8 reg) +{ + u8 data = smb380_read_reg(client, reg); + + data = (data & mask) >> shift; + return data; +} + +/* range */ +static int smb380_set_range(struct i2c_client *client, enum scale_range range) +{ + return smb380_set_reg_bits(client, range, SMB380_RANGE_SHIFT, + SMB380_RANGE_MASK, SMB380_RANGE_BW_REG); +} + +static u8 smb380_get_range(struct i2c_client *client) +{ + return smb380_get_reg_bits(client, SMB380_RANGE_SHIFT, + SMB380_RANGE_MASK, SMB380_RANGE_BW_REG); +} + +/* bandwidth */ +static int smb380_set_bandwidth(struct i2c_client *client, enum filter_bw bw) +{ + return smb380_set_reg_bits(client, bw, SMB380_BANDWIDTH_SHIFT, + SMB380_BANDWIDTH_MASK, SMB380_RANGE_BW_REG); +} + +static u8 smb380_get_bandwidth(struct i2c_client *client) +{ + return smb380_get_reg_bits(client, SMB380_BANDWIDTH_SHIFT, + SMB380_BANDWIDTH_MASK, SMB380_RANGE_BW_REG); +} + +/* new data interrupt */ +static int smb380_set_new_data_int(struct i2c_client *client, u8 val) +{ + return smb380_set_reg_bits(client, val, SMB380_NEW_DATA_INT_SHIFT, + SMB380_NEW_DATA_INT_MASK, SMB380_CONF2_REG); +} + +static u8 smb380_get_new_data_int(struct i2c_client *client) +{ + return smb380_get_reg_bits(client, SMB380_NEW_DATA_INT_SHIFT, + SMB380_NEW_DATA_INT_MASK, SMB380_CONF2_REG); +} + +/* high-g interrupt */ +static int smb380_set_hg_int(struct i2c_client *client, u8 val) +{ + return smb380_set_reg_bits(client, val, SMB380_ENABLE_HG_SHIFT, + SMB380_ENABLE_HG_MASK, SMB380_CTRL2_REG); +} + +static u8 smb380_get_hg_int(struct i2c_client *client) +{ + return smb380_get_reg_bits(client, SMB380_ENABLE_HG_SHIFT, + SMB380_ENABLE_HG_MASK, SMB380_CTRL2_REG); +} + +/* low-g interrupt */ +static int smb380_set_lg_int(struct i2c_client *client, u8 val) +{ + return smb380_set_reg_bits(client, val, SMB380_ENABLE_LG_SHIFT, + SMB380_ENABLE_LG_MASK, SMB380_CTRL2_REG); +} + +static u8 smb380_get_lg_int(struct i2c_client *client) +{ + return smb380_get_reg_bits(client, SMB380_ENABLE_LG_SHIFT, + SMB380_ENABLE_LG_MASK, SMB380_CTRL2_REG); +} + +/* low-g interrupt criterion for duration */ +static int smb380_set_lg_dur(struct i2c_client *client, u8 dur) +{ + return smb380_set_reg_bits(client, dur, SMB380_LG_DUR_SHIFT, + SMB380_LG_DUR_MASK, SMB380_SETTINGS2_REG); +} + +static u8 smb380_get_lg_dur(struct i2c_client *client) +{ + return smb380_get_reg_bits(client, SMB380_LG_DUR_SHIFT, + SMB380_LG_DUR_MASK, SMB380_SETTINGS2_REG); +} + +/* low-g interrupt criterion for threshold */ +static int smb380_set_lg_thres(struct i2c_client *client, u8 thres) +{ + return smb380_set_reg_bits(client, thres, SMB380_LG_THRES_SHIFT, + SMB380_LG_THRES_MASK, SMB380_SETTINGS1_REG); +} + +static u8 smb380_get_lg_thres(struct i2c_client *client) +{ + return smb380_get_reg_bits(client, SMB380_LG_THRES_SHIFT, + SMB380_LG_THRES_MASK, SMB380_SETTINGS1_REG); +} + +/* low-g interrupt criterion for hysteresis */ +static int smb380_set_lg_hyst(struct i2c_client *client, u8 hyst) +{ + return smb380_set_reg_bits(client, hyst, SMB380_LG_HYST_SHIFT, + SMB380_LG_HYST_MASK, SMB380_SETTINGS6_REG); +} + +static u8 smb380_get_lg_hyst(struct i2c_client *client) +{ + return smb380_get_reg_bits(client, SMB380_LG_HYST_SHIFT, + SMB380_LG_HYST_MASK, SMB380_SETTINGS6_REG); +} + +/* high-g interrupt criterion for duration */ +static int smb380_set_hg_dur(struct i2c_client *client, u8 dur) +{ + return smb380_set_reg_bits(client, dur, SMB380_HG_DUR_SHIFT, + SMB380_HG_DUR_MASK, SMB380_SETTINGS4_REG); +} + +static u8 smb380_get_hg_dur(struct i2c_client *client) +{ + return smb380_get_reg_bits(client, SMB380_HG_DUR_SHIFT, + SMB380_HG_DUR_MASK, SMB380_SETTINGS4_REG); +} + +/* high-g interrupt criterion for threshold */ +static int smb380_set_hg_thres(struct i2c_client *client, u8 thres) +{ + return smb380_set_reg_bits(client, thres, SMB380_HG_THRES_SHIFT, + SMB380_HG_THRES_MASK, SMB380_SETTINGS3_REG); +} + +static u8 smb380_get_hg_thres(struct i2c_client *client) +{ + return smb380_get_reg_bits(client, SMB380_HG_THRES_SHIFT, + SMB380_HG_THRES_MASK, SMB380_SETTINGS3_REG); +} + +/* high-g interrupt criterion for hysteresis */ +static int smb380_set_hg_hyst(struct i2c_client *client, u8 hyst) +{ + return smb380_set_reg_bits(client, hyst, SMB380_HG_HYST_SHIFT, + SMB380_HG_HYST_MASK, SMB380_SETTINGS6_REG); +} + +static u8 smb380_get_hg_hyst(struct i2c_client *client) +{ + return smb380_get_reg_bits(client, SMB380_HG_HYST_SHIFT, + SMB380_HG_HYST_MASK, SMB380_SETTINGS6_REG); +} + +static int smb380_set_sleep(struct i2c_client *client, u8 val) +{ + return smb380_set_reg_bits(client, val, SMB380_SLEEP_SHIFT, + SMB380_SLEEP_MASK, SMB380_CTRL1_REG); +} + +/* + * The description of the digital signals x, y and z is "2' complement". + * So we need to correct the sign of data read by i2c. + */ +static void smb380_correct_accel_sign(s16 *val) +{ + *val <<= (sizeof(s16) * BITS_PER_BYTE - SMB380_ACCEL_BITS); + *val >>= (sizeof(s16) * BITS_PER_BYTE - SMB380_ACCEL_BITS); +} + +static void smb380_merge_register_values(struct i2c_client *client, s16 *val, + u8 lsb, u8 msb) +{ + *val = (msb << 2) | (lsb >> 6); + smb380_correct_accel_sign(val); +} + +/* + * Read 8 bit temperature. + * An output of 0 equals -30C, 1 LSB equals 0.5C + */ +static void smb380_read_temperature(struct i2c_client *client, u8 *temper) +{ + *temper = smb380_read_reg(client, SMB380_TEMP_REG); + + dev_dbg(&client->dev, "%s: %d\n", __func__, *temper); +} + +static void smb380_read_xyz(struct i2c_client *client, + s16 *x, s16 *y, s16 *z) +{ + u8 buffer[6]; + buffer[0] = SMB380_X_LSB_REG; + smb380_xyz_read_reg(client, buffer, 6); + + smb380_merge_register_values(client, x, buffer[0], buffer[1]); + smb380_merge_register_values(client, y, buffer[2], buffer[3]); + smb380_merge_register_values(client, z, buffer[4], buffer[5]); + + dev_dbg(&client->dev, "%s: x %d, y %d, z %d\n", __func__, *x, *y, *z); +} + +static ssize_t smb380_show_xyz(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct smb380_sensor *sensor = dev_get_drvdata(dev); + + mutex_lock(&sensor->lock); + smb380_read_xyz(sensor->client, + &sensor->data.x, &sensor->data.y, &sensor->data.z); + mutex_unlock(&sensor->lock); + + return sprintf(buf, "%d, %d, %d\n", + sensor->data.x, sensor->data.y, sensor->data.z); +} +static DEVICE_ATTR(xyz, S_IRUGO, smb380_show_xyz, NULL); + +static ssize_t smb380_show_temper(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct smb380_sensor *sensor = dev_get_drvdata(dev); + + mutex_lock(&sensor->lock); + smb380_read_temperature(sensor->client, &sensor->data.temp); + mutex_unlock(&sensor->lock); + return sprintf(buf, "%d\n", sensor->data.temp); +} +static DEVICE_ATTR(temperature, S_IRUGO, smb380_show_temper, NULL); + +#define SMB380_ADJUST(name) \ +static ssize_t smb380_show_##name(struct device *dev, \ + struct device_attribute *att, char *buf) \ +{ \ + struct smb380_sensor *sensor = dev_get_drvdata(dev); \ + \ + return sprintf(buf, "%d\n", sensor->name); \ +} \ +static ssize_t smb380_store_##name(struct device *dev, \ + struct device_attribute *attr, const char *buf, size_t count) \ +{ \ + struct smb380_sensor *sensor = dev_get_drvdata(dev); \ + unsigned long val; \ + int ret; \ + u8 result; \ + \ + ret = strict_strtoul(buf, 10, &val); \ + if (!ret) { \ + smb380_set_##name(sensor->client, val); \ + result = smb380_get_##name(sensor->client); \ + mutex_lock(&sensor->lock); \ + sensor->name = result; \ + mutex_unlock(&sensor->lock); \ + return count; \ + } \ + else \ + return ret; \ +} \ +static DEVICE_ATTR(name, S_IRUGO | S_IWUSR, \ + smb380_show_##name, smb380_store_##name); + +SMB380_ADJUST(range); +SMB380_ADJUST(bandwidth); +SMB380_ADJUST(new_data_int); +SMB380_ADJUST(hg_int); +SMB380_ADJUST(lg_int); +SMB380_ADJUST(lg_dur); +SMB380_ADJUST(lg_thres); +SMB380_ADJUST(lg_hyst); +SMB380_ADJUST(hg_dur); +SMB380_ADJUST(hg_thres); +SMB380_ADJUST(hg_hyst); + +static struct attribute *smb380_attributes[] = { + &dev_attr_xyz.attr, + &dev_attr_temperature.attr, + &dev_attr_range.attr, + &dev_attr_bandwidth.attr, + &dev_attr_new_data_int.attr, + &dev_attr_hg_int.attr, + &dev_attr_lg_int.attr, + &dev_attr_lg_dur.attr, + &dev_attr_lg_thres.attr, + &dev_attr_lg_hyst.attr, + &dev_attr_hg_dur.attr, + &dev_attr_hg_thres.attr, + &dev_attr_hg_hyst.attr, + NULL +}; + +static const struct attribute_group smb380_group = { + .attrs = smb380_attributes, +}; + +static void smb380_work(struct work_struct *work) +{ + struct smb380_sensor *sensor = + container_of(work, struct smb380_sensor, work); + + smb380_read_xyz(sensor->client, + &sensor->data.x, &sensor->data.y, &sensor->data.z); + smb380_read_temperature(sensor->client, &sensor->data.temp); + + mutex_lock(&sensor->lock); + input_report_abs(sensor->idev, ABS_X, sensor->data.x); + input_report_abs(sensor->idev, ABS_Y, sensor->data.y); + input_report_abs(sensor->idev, ABS_Z, sensor->data.z); + input_sync(sensor->idev); + mutex_unlock(&sensor->lock); + + enable_irq(sensor->client->irq); +} + +static irqreturn_t smb380_irq(int irq, void *dev_id) +{ + struct smb380_sensor *sensor = dev_id; + + if (!work_pending(&sensor->work)) { + disable_irq_nosync(irq); + schedule_work(&sensor->work); + } + + return IRQ_HANDLED; +} + +static void smb380_initialize(struct smb380_sensor *sensor) +{ + smb380_set_range(sensor->client, sensor->range); + smb380_set_bandwidth(sensor->client, sensor->bandwidth); + smb380_set_new_data_int(sensor->client, sensor->new_data_int); + smb380_set_hg_dur(sensor->client, sensor->hg_dur); + smb380_set_hg_thres(sensor->client, sensor->hg_thres); + smb380_set_hg_hyst(sensor->client, sensor->hg_hyst); + smb380_set_lg_dur(sensor->client, sensor->lg_dur); + smb380_set_lg_thres(sensor->client, sensor->lg_thres); + smb380_set_lg_hyst(sensor->client, sensor->lg_hyst); + smb380_set_hg_int(sensor->client, sensor->hg_int); + smb380_set_lg_int(sensor->client, sensor->lg_int); +} + +static void smb380_unregister_input_device(struct smb380_sensor *sensor) +{ + struct i2c_client *client = sensor->client; + + if (client->irq > 0) + free_irq(client->irq, sensor); + + input_unregister_device(sensor->idev); + sensor->idev = NULL; +} + +static int smb380_register_input_device(struct smb380_sensor *sensor) +{ + struct i2c_client *client = sensor->client; + struct input_dev *idev; + int ret; + + idev = sensor->idev = input_allocate_device(); + if (!idev) { + dev_err(&client->dev, "failed to allocate input device\n"); + ret = -ENOMEM; + goto failed_alloc; + } + + idev->name = "SMB380 Sensor"; + idev->id.bustype = BUS_I2C; + idev->dev.parent = &client->dev; + idev->evbit[0] = BIT_MASK(EV_ABS); + + input_set_abs_params(idev, ABS_X, SMB380_MIN_VALUE, + SMB380_MAX_VALUE, 0, 0); + input_set_abs_params(idev, ABS_Y, SMB380_MIN_VALUE, + SMB380_MAX_VALUE, 0, 0); + input_set_abs_params(idev, ABS_Z, SMB380_MIN_VALUE, + SMB380_MAX_VALUE, 0, 0); + + input_set_drvdata(idev, sensor); + + ret = input_register_device(idev); + if (ret) { + dev_err(&client->dev, "failed to register input device\n"); + goto failed_reg; + } + + if (client->irq > 0) { + ret = request_irq(client->irq, smb380_irq, IRQF_TRIGGER_RISING, + "smb380 accelerometer", sensor); + if (ret) { + dev_err(&client->dev, "can't get IRQ %d, ret %d\n", + client->irq, ret); + goto failed_irq; + } + } + + return 0; + +failed_irq: + input_unregister_device(idev); + idev = NULL; +failed_reg: + if (idev) + input_free_device(idev); +failed_alloc: + return ret; +} + +static int __devinit smb380_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct smb380_sensor *sensor; + struct smb380_platform_data *pdata; + int ret; + + sensor = kzalloc(sizeof(struct smb380_sensor), GFP_KERNEL); + if (!sensor) { + dev_err(&client->dev, "failed to allocate driver data\n"); + return -ENOMEM; + } + + pdata = client->dev.platform_data; + + sensor->client = client; + i2c_set_clientdata(client, sensor); + + ret = smb380_read_reg(client, SMB380_CHIP_ID_REG); + if (ret < 0) { + dev_err(&client->dev, "failed to detect device\n"); + goto failed_free; + } + if (ret != SMB380_CHIP_ID) { + dev_err(&client->dev, "unsupported chip id\n"); + goto failed_free; + } + + INIT_WORK(&sensor->work, smb380_work); + mutex_init(&sensor->lock); + + ret = sysfs_create_group(&client->dev.kobj, &smb380_group); + if (ret) { + dev_err(&client->dev, "failed to create attribute group\n"); + goto failed_free; + } + + ret = smb380_register_input_device(sensor); + if (ret) + goto failed_remove_sysfs; + + if (pdata) { + sensor->range = pdata->range; + sensor->bandwidth = pdata->bandwidth; + sensor->new_data_int = pdata->new_data_int; + sensor->hg_int = pdata->hg_int; + sensor->lg_int = pdata->lg_int; + sensor->hg_dur = pdata->hg_dur; + sensor->hg_thres = pdata->hg_thres; + sensor->hg_hyst = pdata->hg_hyst; + sensor->lg_dur = pdata->lg_dur; + sensor->lg_thres = pdata->lg_thres; + sensor->lg_hyst = pdata->lg_hyst; + } else { + sensor->range = SMB380_DEFAULT_RANGE; + sensor->bandwidth = SMB380_DEFAULT_BANDWIDTH; + sensor->new_data_int = SMB380_DEFAULT_NEW_DATA_INT; + sensor->hg_int = SMB380_DEFAULT_HG_INT; + sensor->lg_int = SMB380_DEFAULT_LG_INT; + sensor->hg_dur = SMB380_DEFAULT_HG_DURATION; + sensor->hg_thres = SMB380_DEFAULT_HG_THRESHOLD; + sensor->hg_hyst = SMB380_DEFAULT_HG_HYST; + sensor->lg_dur = SMB380_DEFAULT_LG_DURATION; + sensor->lg_thres = SMB380_DEFAULT_LG_THRESHOLD; + sensor->lg_hyst = SMB380_DEFAULT_LG_HYST; + } + + smb380_initialize(sensor); + + dev_info(&client->dev, "%s registered\n", id->name); + return 0; + +failed_remove_sysfs: + sysfs_remove_group(&client->dev.kobj, &smb380_group); +failed_free: + kfree(sensor); + return ret; +} + +static int __devexit smb380_remove(struct i2c_client *client) +{ + struct smb380_sensor *sensor = i2c_get_clientdata(client); + + smb380_unregister_input_device(sensor); + sysfs_remove_group(&client->dev.kobj, &smb380_group); + kfree(sensor); + return 0; +} + +#ifdef CONFIG_PM +static int smb380_suspend(struct i2c_client *client, pm_message_t mesg) +{ + smb380_set_sleep(client, 1); + return 0; +} + +static int smb380_resume(struct i2c_client *client) +{ + smb380_set_sleep(client, 0); + return 0; +} + +#else +#define smb380_suspend NULL +#define smb380_resume NULL +#endif + +static const struct i2c_device_id smb380_ids[] = { + { "smb380", 0 }, + { "bma023", 1 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, smb380_ids); + +static struct i2c_driver smb380_i2c_driver = { + .driver = { + .name = "smb380", + }, + .probe = smb380_probe, + .remove = __devexit_p(smb380_remove), + .suspend = smb380_suspend, + .resume = smb380_resume, + .id_table = smb380_ids, +}; + +static int __init smb380_init(void) +{ + return i2c_add_driver(&smb380_i2c_driver); +} +module_init(smb380_init); + +static void __exit smb380_exit(void) +{ + i2c_del_driver(&smb380_i2c_driver); +} +module_exit(smb380_exit); + +MODULE_AUTHOR("Kim Kyuwon "); +MODULE_DESCRIPTION("SMB380/BMA023 Tri-axis accelerometer driver"); +MODULE_LICENSE("GPL"); diff --git a/include/linux/smb380.h b/include/linux/smb380.h new file mode 100644 index 0000000..a6f971b --- /dev/null +++ b/include/linux/smb380.h @@ -0,0 +1,47 @@ +/* + * smb380.h - SMB380 Tri-axis accelerometer driver + * + * Copyright (c) 2010 Samsung Eletronics + * Kyungmin Park + * + * 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. + * + */ + +#ifndef _SMB380_H_ +#define _SMB380_H_ + +enum scale_range { + RANGE_2G, + RANGE_4G, + RANGE_8G, +}; + +/* Used to setup the digital filtering bandwidth of ADC output */ +enum filter_bw { + BW_25HZ, + BW_50HZ, + BW_100HZ, + BW_190HZ, + BW_375HZ, + BW_750HZ, + BW_1500HZ, +}; + +struct smb380_platform_data { + enum scale_range range; + enum filter_bw bandwidth; + u8 new_data_int; + u8 hg_int; + u8 lg_int; + u8 lg_dur; + u8 lg_thres; + u8 lg_hyst; + u8 hg_dur; + u8 hg_thres; + u8 hg_hyst; +}; + +#endif /* _SMB380_H_ */