diff options
Diffstat (limited to 'drivers/sensor/k3g.c')
-rw-r--r-- | drivers/sensor/k3g.c | 1372 |
1 files changed, 1372 insertions, 0 deletions
diff --git a/drivers/sensor/k3g.c b/drivers/sensor/k3g.c new file mode 100644 index 0000000..7ca6d6f --- /dev/null +++ b/drivers/sensor/k3g.c @@ -0,0 +1,1372 @@ +/* + * Copyright (C) 2010, Samsung Electronics Co. Ltd. All Rights Reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * 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/kernel.h> +#include <linux/module.h> +#include <linux/i2c.h> +#include <linux/uaccess.h> +#include <linux/poll.h> +#include <linux/slab.h> +#include <linux/input.h> +#include <linux/interrupt.h> +#include <asm/div64.h> +#include <linux/delay.h> +#include <linux/completion.h> +#include <linux/sensor/k3g.h> + +/* k3g chip id */ +#define DEVICE_ID 0xD3 + +/* k3g gyroscope registers */ +#define WHO_AM_I 0x0F +#define CTRL_REG1 0x20 /* power control reg */ +#define CTRL_REG2 0x21 /* power control reg */ +#define CTRL_REG3 0x22 /* power control reg */ +#define CTRL_REG4 0x23 /* interrupt control reg */ +#define CTRL_REG5 0x24 /* interrupt control reg */ +#define OUT_TEMP 0x26 /* Temperature data */ +#define STATUS_REG 0x27 +#define AXISDATA_REG 0x28 +#define OUT_Y_L 0x2A +#define FIFO_CTRL_REG 0x2E +#define FIFO_SRC_REG 0x2F +#define PM_OFF 0x00 +#define PM_NORMAL 0x08 +#define ENABLE_ALL_AXES 0x07 +#define BYPASS_MODE 0x00 +#define FIFO_MODE 0x20 +#define FIFO_EMPTY 0x20 +#define AC (1 << 7) /* register auto-increment bit */ + +/* odr settings */ +#define FSS_MASK 0x1F +#define ODR_MASK 0xF0 +#define ODR105_BW12_5 0x00 /* ODR = 105Hz; BW = 12.5Hz */ +#define ODR105_BW25 0x10 /* ODR = 105Hz; BW = 25Hz */ +#define ODR210_BW12_5 0x40 /* ODR = 210Hz; BW = 12.5Hz */ +#define ODR210_BW25 0x50 /* ODR = 210Hz; BW = 25Hz */ +#define ODR210_BW50 0x60 /* ODR = 210Hz; BW = 50Hz */ +#define ODR210_BW70 0x70 /* ODR = 210Hz; BW = 70Hz */ +#define ODR420_BW20 0x80 /* ODR = 420Hz; BW = 20Hz */ +#define ODR420_BW25 0x90 /* ODR = 420Hz; BW = 25Hz */ +#define ODR420_BW50 0xA0 /* ODR = 420Hz; BW = 50Hz */ +#define ODR420_BW110 0xB0 /* ODR = 420Hz; BW = 110Hz */ +#define ODR840_BW30 0xC0 /* ODR = 840Hz; BW = 30Hz */ +#define ODR840_BW35 0xD0 /* ODR = 840Hz; BW = 35Hz */ +#define ODR840_BW50 0xE0 /* ODR = 840Hz; BW = 50Hz */ +#define ODR840_BW110 0xF0 /* ODR = 840Hz; BW = 110Hz */ + +/* full scale selection */ +#define DPS250 250 +#define DPS500 500 +#define DPS2000 2000 +#define FS_MASK 0x30 +#define FS_250DPS 0x00 +#define FS_500DPS 0x10 +#define FS_2000DPS 0x20 +#define DEFAULT_DPS DPS500 +#define FS_DEFULAT_DPS FS_500DPS + +/* self tset settings */ +#define MIN_ST 175 +#define MAX_ST 875 +#define FIFO_TEST_WTM 0x1F +#define MIN_ZERO_RATE -1714 +#define MAX_ZERO_RATE 1714 + +/* max and min entry */ +#define MAX_ENTRY 20 +#define MAX_DELAY (MAX_ENTRY * 9523809LL) + +#define K3G_MAJOR 102 +#define K3G_MINOR 4 + +/* default register setting for device init */ +static const char default_ctrl_regs[] = { + 0x3F, /* 105HZ, PM-normal, xyz enable */ + 0x00, /* normal mode */ + 0x04, /* fifo wtm interrupt on */ + 0x90, /* block data update, 500d/s */ + 0x40, /* fifo enable */ +}; + +static const struct odr_delay { + u8 odr; /* odr reg setting */ + u32 delay_ns; /* odr in ns */ +} odr_delay_table[] = { +/* { ODR840_BW110, 1190476LL }, */ /* 840Hz */ +/* { ODR420_BW110, 2380952LL }, */ /* 420Hz */ + { ODR210_BW70, 4761904LL }, /* 210Hz */ + { ODR105_BW25, 9523809LL }, /* 105Hz */ +}; + +/* + * K3G gyroscope data + * brief structure containing gyroscope values for yaw, pitch and roll in + * signed short + */ +struct k3g_t { + s16 x; + s16 y; + s16 z; +}; + +struct k3g_data { + struct i2c_client *client; + struct input_dev *input_dev; + struct mutex lock; + struct workqueue_struct *k3g_wq; + struct work_struct work; + struct hrtimer timer; + struct class *k3g_gyro_dev_class; + struct device *dev; + struct completion data_ready; + bool enable; + bool drop_next_event; + bool fifo_test; /* is self_test or not? */ + bool interruptible; /* interrupt or polling? */ + int entries; /* number of fifo entries */ + int dps; /* scale selection */ + u8 fifo_data[sizeof(struct k3g_t) * 32]; /* fifo data entries */ + u8 ctrl_regs[5]; /* saving register settings */ + u32 time_to_read; /* time needed to read one entry */ + ktime_t polling_delay; /* polling time for timer */ +}; + +static int k3g_read_fifo_status(struct k3g_data *k3g_data) +{ + int fifo_status; + + fifo_status = i2c_smbus_read_byte_data(k3g_data->client, FIFO_SRC_REG); + if (fifo_status < 0) { + pr_err("%s: failed to read fifo source register\n", + __func__); + return fifo_status; + } + return (fifo_status & FSS_MASK) + !(fifo_status & FIFO_EMPTY); +} + +static int k3g_restart_fifo(struct k3g_data *k3g_data) +{ + int res = 0; + + res = i2c_smbus_write_byte_data(k3g_data->client, + FIFO_CTRL_REG, BYPASS_MODE); + if (res < 0) { + pr_err("%s : failed to set bypass_mode\n", __func__); + return res; + } + + res = i2c_smbus_write_byte_data(k3g_data->client, + FIFO_CTRL_REG, FIFO_MODE | (k3g_data->entries - 1)); + + if (res < 0) + pr_err("%s : failed to set fifo_mode\n", __func__); + + return res; +} + +static void set_polling_delay(struct k3g_data *k3g_data, int res) +{ + s64 delay_ns; + + delay_ns = k3g_data->entries + 1 - res; + if (delay_ns < 0) + delay_ns = 0; + + delay_ns = delay_ns * k3g_data->time_to_read; + k3g_data->polling_delay = ns_to_ktime(delay_ns); +} + +/* gyroscope data readout */ +static int k3g_read_gyro_values(struct i2c_client *client, + struct k3g_t *data, int total_read) +{ + int err; + int len = sizeof(*data) * (total_read ? (total_read - 1) : 1); + struct k3g_data *k3g_data = i2c_get_clientdata(client); + struct i2c_msg msg[2]; + u8 reg_buf; + + msg[0].addr = client->addr; + msg[0].buf = ®_buf; + msg[0].flags = 0; + msg[0].len = 1; + + msg[1].addr = client->addr; + msg[1].flags = I2C_M_RD; + msg[1].buf = k3g_data->fifo_data; + + if (total_read > 1) { + reg_buf = AXISDATA_REG | AC; + msg[1].len = len; + + err = i2c_transfer(client->adapter, msg, 2); + if (err != 2) + return (err < 0) ? err : -EIO; + } + + reg_buf = AXISDATA_REG; + msg[1].len = 1; + err = i2c_transfer(client->adapter, msg, 2); + if (err != 2) + return (err < 0) ? err : -EIO; + + reg_buf = OUT_Y_L | AC; + msg[1].len = sizeof(*data); + err = i2c_transfer(client->adapter, msg, 2); + if (err != 2) + return (err < 0) ? err : -EIO; + + data->y = (k3g_data->fifo_data[1] << 8) | k3g_data->fifo_data[0]; + data->z = (k3g_data->fifo_data[3] << 8) | k3g_data->fifo_data[2]; + data->x = (k3g_data->fifo_data[5] << 8) | k3g_data->fifo_data[4]; + + return 0; +} + +static int k3g_report_gyro_values(struct k3g_data *k3g_data) +{ + int res; + struct k3g_t data; + + res = k3g_read_gyro_values(k3g_data->client, &data, + k3g_data->entries + k3g_data->drop_next_event); + if (res < 0) + return res; + + res = k3g_read_fifo_status(k3g_data); + + k3g_data->drop_next_event = !res; + + if (res >= 31 - k3g_data->entries) { + /* reset fifo to start again - data isn't trustworthy, + * our locked read might not have worked and we + * could have done i2c read in mid register update + */ + return k3g_restart_fifo(k3g_data); + } + + input_report_rel(k3g_data->input_dev, REL_RX, data.x); + input_report_rel(k3g_data->input_dev, REL_RY, data.y); + input_report_rel(k3g_data->input_dev, REL_RZ, data.z); + input_sync(k3g_data->input_dev); + + return res; +} + +static enum hrtimer_restart k3g_timer_func(struct hrtimer *timer) +{ + struct k3g_data *k3g_data = container_of(timer, struct k3g_data, timer); + queue_work(k3g_data->k3g_wq, &k3g_data->work); + return HRTIMER_NORESTART; +} + +static void k3g_work_func(struct work_struct *work) +{ + int res; + struct k3g_data *k3g_data = container_of(work, struct k3g_data, work); + + do { + res = k3g_read_fifo_status(k3g_data); + if (res < 0) + return; + + if (res < k3g_data->entries) { + pr_warn("%s: fifo entries are less than we want\n", + __func__); + goto timer_set; + } + + res = k3g_report_gyro_values(k3g_data); + if (res < 0) + return; +timer_set: + set_polling_delay(k3g_data, res); + + } while (!ktime_to_ns(k3g_data->polling_delay)); + + hrtimer_start(&k3g_data->timer, + k3g_data->polling_delay, HRTIMER_MODE_REL); +} + +static irqreturn_t k3g_interrupt_thread(int irq, void *k3g_data_p) +{ + int res; + struct k3g_data *k3g_data = k3g_data_p; + + if (unlikely(k3g_data->fifo_test)) { + disable_irq_nosync(irq); + complete(&k3g_data->data_ready); + return IRQ_HANDLED; + } + + res = k3g_report_gyro_values(k3g_data); + if (res < 0) + pr_err("%s: failed to report gyro values\n", __func__); + + return IRQ_HANDLED; +} + +static ssize_t k3g_selftest_dps_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct k3g_data *k3g_data = dev_get_drvdata(dev); + return sprintf(buf, "%d\n", k3g_data->dps); +} +static ssize_t k3g_force_sleep(struct device *dev, + struct device_attribute *attr, char *buf) +{ + int err = 5; + struct i2c_client *client = to_i2c_client(dev); + struct k3g_data *k3g_data = i2c_get_clientdata(client); + + err = i2c_smbus_write_byte_data(k3g_data->client, + CTRL_REG1, 0x00); + if (err == 0) + err = 1; + else + err = 0; + + return sprintf(buf, "%d\n", err); +} +static ssize_t k3g_selftest_dps_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct k3g_data *k3g_data = dev_get_drvdata(dev); + int new_dps = 0; + int err; + u8 ctrl; + + sscanf(buf, "%d", &new_dps); + + /* check out dps validity */ + if (new_dps != DPS250 && new_dps != DPS500 && new_dps != DPS2000) { + pr_err("%s: wrong dps(%d)\n", __func__, new_dps); + return -1; + } + + ctrl = (k3g_data->ctrl_regs[3] & ~FS_MASK); + + if (new_dps == DPS250) + ctrl |= FS_250DPS; + else if (new_dps == DPS500) + ctrl |= FS_500DPS; + else if (new_dps == DPS2000) + ctrl |= FS_2000DPS; + else + ctrl |= FS_DEFULAT_DPS; + + /* apply new dps */ + mutex_lock(&k3g_data->lock); + k3g_data->ctrl_regs[3] = ctrl; + + err = i2c_smbus_write_byte_data(k3g_data->client, CTRL_REG4, ctrl); + if (err < 0) { + pr_err("%s: updating dps failed\n", __func__); + mutex_unlock(&k3g_data->lock); + return err; + } + mutex_unlock(&k3g_data->lock); + + k3g_data->dps = new_dps; + pr_err("%s: %d dps stored\n", __func__, k3g_data->dps); + + return count; +} + +static ssize_t k3g_show_enable(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct k3g_data *k3g_data = dev_get_drvdata(dev); + return sprintf(buf, "%d\n", k3g_data->enable); +} + +static ssize_t k3g_set_enable(struct device *dev, + struct device_attribute *attr, const char *buf, size_t size) +{ + int err = 0; + struct k3g_data *k3g_data = dev_get_drvdata(dev); + bool new_enable; + + if (sysfs_streq(buf, "1")) + new_enable = true; + else if (sysfs_streq(buf, "0")) + new_enable = false; + else { + pr_debug("%s: invalid value %d\n", __func__, *buf); + return -EINVAL; + } + + if (new_enable == k3g_data->enable) + return size; + + mutex_lock(&k3g_data->lock); + if (new_enable) { + err = i2c_smbus_write_i2c_block_data(k3g_data->client, + CTRL_REG1 | AC, sizeof(k3g_data->ctrl_regs), + k3g_data->ctrl_regs); + if (err < 0) { + err = -EIO; + goto unlock; + } + + /* reset fifo entries */ + err = k3g_restart_fifo(k3g_data); + if (err < 0) { + err = -EIO; + goto turn_off; + } + + if (k3g_data->interruptible) + enable_irq(k3g_data->client->irq); + else { + set_polling_delay(k3g_data, 0); + hrtimer_start(&k3g_data->timer, + k3g_data->polling_delay, HRTIMER_MODE_REL); + } + } else { + if (k3g_data->interruptible) + disable_irq(k3g_data->client->irq); + else { + hrtimer_cancel(&k3g_data->timer); + cancel_work_sync(&k3g_data->work); + } + /* turning off */ + err = i2c_smbus_write_byte_data(k3g_data->client, + CTRL_REG1, 0x00); + if (err < 0) + goto unlock; + } + k3g_data->enable = new_enable; + +turn_off: + if (err < 0) + i2c_smbus_write_byte_data(k3g_data->client, + CTRL_REG1, 0x00); +unlock: + mutex_unlock(&k3g_data->lock); + + return err ? err : size; +} + +static ssize_t k3g_show_delay(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct k3g_data *k3g_data = dev_get_drvdata(dev); + u64 delay; + + delay = k3g_data->time_to_read * k3g_data->entries; + delay = ktime_to_ns(ns_to_ktime(delay)); + + return sprintf(buf, "%lld\n", delay); +} + +static ssize_t k3g_set_delay(struct device *dev, + struct device_attribute *attr, const char *buf, size_t size) +{ + struct k3g_data *k3g_data = dev_get_drvdata(dev); + int odr_value = ODR105_BW25; + int res = 0; + int i; + u64 delay_ns; + u8 ctrl; + + res = strict_strtoll(buf, 10, &delay_ns); + if (res < 0) + return res; + + mutex_lock(&k3g_data->lock); + if (!k3g_data->interruptible) + hrtimer_cancel(&k3g_data->timer); + else + disable_irq(k3g_data->client->irq); + + /* round to the nearest supported ODR that is less than + * the requested value + */ + for (i = 0; i < ARRAY_SIZE(odr_delay_table); i++) + if (delay_ns <= odr_delay_table[i].delay_ns) { + odr_value = odr_delay_table[i].odr; + delay_ns = odr_delay_table[i].delay_ns; + k3g_data->time_to_read = delay_ns; + k3g_data->entries = 1; + break; + } + + if (delay_ns >= odr_delay_table[1].delay_ns) { + if (delay_ns >= MAX_DELAY) { + k3g_data->entries = MAX_ENTRY; + delay_ns = MAX_DELAY; + } else { + do_div(delay_ns, odr_delay_table[1].delay_ns); + k3g_data->entries = delay_ns; + } + k3g_data->time_to_read = odr_delay_table[1].delay_ns; + } + + if (odr_value != (k3g_data->ctrl_regs[0] & ODR_MASK)) { + ctrl = (k3g_data->ctrl_regs[0] & ~ODR_MASK); + ctrl |= odr_value; + k3g_data->ctrl_regs[0] = ctrl; + res = i2c_smbus_write_byte_data(k3g_data->client, + CTRL_REG1, ctrl); + } + + /* (re)start fifo */ + k3g_restart_fifo(k3g_data); + + if (!k3g_data->interruptible) { + delay_ns = k3g_data->entries * k3g_data->time_to_read; + k3g_data->polling_delay = ns_to_ktime(delay_ns); + if (k3g_data->enable) + hrtimer_start(&k3g_data->timer, + k3g_data->polling_delay, HRTIMER_MODE_REL); + } else + enable_irq(k3g_data->client->irq); + + mutex_unlock(&k3g_data->lock); + + return size; +} + +static DEVICE_ATTR(enable, S_IRUGO | S_IWUSR | S_IWGRP, + k3g_show_enable, k3g_set_enable); +static DEVICE_ATTR(poll_delay, S_IRUGO | S_IWUSR | S_IWGRP, + k3g_show_delay, k3g_set_delay); + + +/*************************************************************************/ +/* K3G Sysfs */ +/*************************************************************************/ + +/* Device Initialization */ +static int device_init(struct k3g_data *data) +{ + int err; + u8 buf[5]; + + buf[0] = 0x6f; + buf[1] = 0x00; + buf[2] = 0x00; + buf[3] = 0x00; + buf[4] = 0x00; + err = i2c_smbus_write_i2c_block_data(data->client, + CTRL_REG1 | AC, sizeof(buf), buf); + if (err < 0) + pr_err("%s: CTRL_REGs i2c writing failed\n", __func__); + + return err; +} + +static ssize_t k3g_power_on(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct k3g_data *data = dev_get_drvdata(dev); + int err; + + err = device_init(data); + if (err < 0) { + pr_err("%s: device_init() failed\n", __func__); + return 0; + } + + printk(KERN_INFO "[%s] result of device init = %d\n", __func__, err); + + return sprintf(buf, "%d\n", (err < 0 ? 0 : 1)); +} + +static ssize_t k3g_get_temp(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct k3g_data *data = dev_get_drvdata(dev); + char temp; + + temp = i2c_smbus_read_byte_data(data->client, OUT_TEMP); + if (temp < 0) { + pr_err("%s: STATUS_REGS i2c reading failed\n", __func__); + return 0; + } + + printk(KERN_INFO "[%s] read temperature : %d\n", __func__, temp); + + return sprintf(buf, "%d\n", temp); +} + +static int k3g_fifo_self_test(struct k3g_data *k3g_data) +{ + struct k3g_t raw_data; + int err; + int i, j; + s16 raw[3] = { 0, }; + u8 reg[5]; + u8 fifo_pass = 2; + u8 status_reg; + + k3g_data->fifo_test = true; + + /* fifo mode, enable interrupt, 500dps */ + reg[0] = 0x6F; + reg[1] = 0x00; + reg[2] = 0x04; + reg[3] = 0x90; + reg[4] = 0x40; + + for (i = 0; i < 10; i++) { + err = i2c_smbus_write_i2c_block_data(k3g_data->client, + CTRL_REG1 | AC, sizeof(reg), reg); + if (err >= 0) + break; + } + if (err < 0) { + pr_err("%s: CTRL_REGs i2c writing failed\n", __func__); + goto exit; + } + + /* Power up, wait for 800ms for stable output */ + msleep(800); + + for (i = 0; i < 10; i++) { + err = i2c_smbus_write_byte_data(k3g_data->client, + FIFO_CTRL_REG, BYPASS_MODE); + if (err >= 0) + break; + } + if (err < 0) { + pr_err("%s : failed to set bypass_mode\n", __func__); + goto exit; + } + + for (i = 0; i < 10; i++) { + err = i2c_smbus_write_byte_data(k3g_data->client, + FIFO_CTRL_REG, FIFO_MODE | FIFO_TEST_WTM); + if (err >= 0) + break; + } + if (err < 0) { + pr_err("%s: failed to set fifo_mode\n", __func__); + goto exit; + } + + /* if interrupt mode */ + if (!k3g_data->enable && k3g_data->interruptible) { + enable_irq(k3g_data->client->irq); + err = wait_for_completion_timeout(&k3g_data->data_ready, 5*HZ); + if (err <= 0) { + disable_irq(k3g_data->client->irq); + if (!err) + pr_err("%s: wait timed out\n", __func__); + goto exit; + } + } + + /* wait for 32 fifo entries */ + msleep(200); + + /* check out watermark status */ + status_reg = i2c_smbus_read_byte_data(k3g_data->client, FIFO_SRC_REG); + if (!(status_reg & 0x80)) { + pr_err("%s: Watermark level is not enough\n", __func__); + goto exit; + } + + /* read fifo entries */ + err = k3g_read_gyro_values(k3g_data->client, + &raw_data, FIFO_TEST_WTM + 2); + if (err < 0) { + pr_err("%s: k3g_read_gyro_values() failed\n", __func__); + goto exit; + } + + /* print out fifo data */ + printk(KERN_INFO "[gyro_self_test] fifo data\n"); + for (i = 0; i < sizeof(raw_data) * (FIFO_TEST_WTM + 1); + i += sizeof(raw_data)) { + raw[0] = (k3g_data->fifo_data[i+1] << 8) + | k3g_data->fifo_data[i]; + raw[1] = (k3g_data->fifo_data[i+3] << 8) + | k3g_data->fifo_data[i+2]; + raw[2] = (k3g_data->fifo_data[i+5] << 8) + | k3g_data->fifo_data[i+4]; + pr_err("%2dth: %8d %8d %8d\n", i/6, raw[0], raw[1], raw[2]); + + for (j = 0; j < 3; j++) { + if (raw[j] < MIN_ZERO_RATE || raw[j] > MAX_ZERO_RATE) { + pr_err("%s: %dth data(%d) is out of zero-rate", + __func__, i/6, raw[j]); + pr_err("%s: fifo test failed\n", __func__); + fifo_pass = 0; + goto exit; + } + } + } + + fifo_pass = 1; + +exit: + k3g_data->fifo_test = false; + + /* make sure clearing interrupt */ + enable_irq(k3g_data->client->irq); + disable_irq(k3g_data->client->irq); + + /* 1: success, 0: fail, 2: retry */ + return fifo_pass; +} + +static int k3g_bypass_self_test(struct k3g_data *k3g_data, + int NOST[3], int ST[3]) +{ + int differ_x = 0, differ_y = 0, differ_z = 0; + int err; + int i, j; + s16 raw[3] = { 0, }; + s32 temp; + u8 gyro_data[6] = { 0, }; + u8 reg[5]; + u8 bZYXDA = 0; + u8 bypass_pass = 2; + + /* Initialize Sensor, turn on sensor, enable P/R/Y */ + /* Set BDU=1, Set ODR=200Hz, Cut-Off Frequency=50Hz, FS=2000dps */ + reg[0] = 0x6f; + reg[1] = 0x00; + reg[2] = 0x00; + reg[3] = 0xA0; + reg[4] = 0x02; + + for (i = 0; i < 10; i++) { + err = i2c_smbus_write_i2c_block_data(k3g_data->client, + CTRL_REG1 | AC, sizeof(reg), reg); + if (err >= 0) + break; + } + if (err < 0) { + pr_err("%s: CTRL_REGs i2c writing failed\n", __func__); + goto exit; + } + + for (i = 0; i < 10; i++) { + err = i2c_smbus_write_byte_data(k3g_data->client, + FIFO_CTRL_REG, BYPASS_MODE); + if (err >= 0) + break; + } + if (err < 0) { + pr_err("%s : failed to set bypass_mode\n", __func__); + goto exit; + } + + /* Power up, wait for 800ms for stable output */ + msleep(800); + + /* Read 5 samples output before self-test on */ + for (i = 0; i < 5; i++) { + /* check ZYXDA ready bit */ + for (j = 0; j < 10; j++) { + temp = i2c_smbus_read_byte_data(k3g_data->client, + STATUS_REG); + if (temp >= 0) { + bZYXDA = temp & 0x08; + if (!bZYXDA) { + usleep_range(10000, 10000); + pr_err("%s: %d,%d: no_data_ready", + __func__, i, j); + continue; + } else + break; + } + } + if (temp < 0) { + pr_err("%s: STATUS_REGS i2c reading failed\n", + __func__); + goto exit; + } + + for (j = 0; j < 10; j++) { + err = i2c_smbus_read_i2c_block_data(k3g_data->client, + AXISDATA_REG | AC, + sizeof(gyro_data), gyro_data); + if (err >= 0) + break; + } + if (err < 0) { + pr_err("%s: CTRL_REGs i2c reading failed\n", __func__); + goto exit; + } + + raw[0] = (gyro_data[1] << 8) | gyro_data[0]; + raw[1] = (gyro_data[3] << 8) | gyro_data[2]; + raw[2] = (gyro_data[5] << 8) | gyro_data[4]; + + NOST[0] += raw[0]; + NOST[1] += raw[1]; + NOST[2] += raw[2]; + + printk(KERN_INFO "[gyro_self_test] raw[0] = %d\n", raw[0]); + printk(KERN_INFO "[gyro_self_test] raw[1] = %d\n", raw[1]); + printk(KERN_INFO "[gyro_self_test] raw[2] = %d\n\n", raw[2]); + } + + for (i = 0; i < 3; i++) + printk(KERN_INFO "[gyro_self_test] " + "SUM of NOST[%d] = %d\n", i, NOST[i]); + + /* calculate average of NOST and covert from ADC to DPS */ + for (i = 0; i < 3; i++) { + NOST[i] = (NOST[i] / 5) * 70 / 1000; + printk(KERN_INFO "[gyro_self_test] " + "AVG of NOST[%d] = %d\n", i, NOST[i]); + } + printk(KERN_INFO "\n"); + + /* Enable Self Test */ + reg[0] = 0xA2; + for (i = 0; i < 10; i++) { + err = i2c_smbus_write_byte_data(k3g_data->client, + CTRL_REG4, reg[0]); + if (err >= 0) + break; + } + if (temp < 0) { + pr_err("%s: CTRL_REG4 i2c writing failed\n", __func__); + goto exit; + } + + msleep(100); + + /* Read 5 samples output after self-test on */ + for (i = 0; i < 5; i++) { + /* check ZYXDA ready bit */ + for (j = 0; j < 10; j++) { + temp = i2c_smbus_read_byte_data(k3g_data->client, + STATUS_REG); + if (temp >= 0) { + bZYXDA = temp & 0x08; + if (!bZYXDA) { + usleep_range(10000, 10000); + pr_err("%s: %d,%d: no_data_ready", + __func__, i, j); + continue; + } else + break; + } + + } + if (temp < 0) { + pr_err("%s: STATUS_REGS i2c reading failed\n", + __func__); + goto exit; + } + + for (j = 0; j < 10; j++) { + err = i2c_smbus_read_i2c_block_data(k3g_data->client, + AXISDATA_REG | AC, + sizeof(gyro_data), gyro_data); + if (err >= 0) + break; + } + if (err < 0) { + pr_err("%s: CTRL_REGs i2c reading failed\n", __func__); + goto exit; + } + + raw[0] = (gyro_data[1] << 8) | gyro_data[0]; + raw[1] = (gyro_data[3] << 8) | gyro_data[2]; + raw[2] = (gyro_data[5] << 8) | gyro_data[4]; + + ST[0] += raw[0]; + ST[1] += raw[1]; + ST[2] += raw[2]; + + printk(KERN_INFO "[gyro_self_test] raw[0] = %d\n", raw[0]); + printk(KERN_INFO "[gyro_self_test] raw[1] = %d\n", raw[1]); + printk(KERN_INFO "[gyro_self_test] raw[2] = %d\n\n", raw[2]); + } + + for (i = 0; i < 3; i++) + printk(KERN_INFO "[gyro_self_test] " + "SUM of ST[%d] = %d\n", i, ST[i]); + + /* calculate average of ST and convert from ADC to dps */ + for (i = 0; i < 3; i++) { + /* When FS=2000, 70 mdps/digit */ + ST[i] = (ST[i] / 5) * 70 / 1000; + printk(KERN_INFO "[gyro_self_test] " + "AVG of ST[%d] = %d\n", i, ST[i]); + } + + /* check whether pass or not */ + if (ST[0] >= NOST[0]) /* for x */ + differ_x = ST[0] - NOST[0]; + else + differ_x = NOST[0] - ST[0]; + + if (ST[1] >= NOST[1]) /* for y */ + differ_y = ST[1] - NOST[1]; + else + differ_y = NOST[1] - ST[1]; + + if (ST[2] >= NOST[2]) /* for z */ + differ_z = ST[2] - NOST[2]; + else + differ_z = NOST[2] - ST[2]; + + printk(KERN_INFO "[gyro_self_test] differ x:%d, y:%d, z:%d\n", + differ_x, differ_y, differ_z); + + if ((MIN_ST <= differ_x && differ_x <= MAX_ST) + && (MIN_ST <= differ_y && differ_y <= MAX_ST) + && (MIN_ST <= differ_z && differ_z <= MAX_ST)) + bypass_pass = 1; + else + bypass_pass = 0; + +exit: + return bypass_pass; +} + +static ssize_t k3g_self_test(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct k3g_data *data = dev_get_drvdata(dev); + int NOST[3] = { 0, }, ST[3] = { 0, }; + int err; + int i; + u8 backup_regs[5]; + u8 fifo_pass = 2; + u8 bypass_pass = 2; + + /* before starting self-test, backup register */ + for (i = 0; i < 10; i++) { + err = i2c_smbus_read_i2c_block_data(data->client, + CTRL_REG1 | AC, sizeof(backup_regs), backup_regs); + if (err >= 0) + break; + } + if (err < 0) { + pr_err("%s: CTRL_REGs i2c reading failed\n", __func__); + goto exit; + } + + for (i = 0; i < 5; i++) + printk(KERN_INFO "[gyro_self_test] " + "backup reg[%d] = %2x\n", i, backup_regs[i]); + + /* fifo self test */ + printk(KERN_INFO "\n[gyro_self_test] fifo self-test\n"); + + fifo_pass = k3g_fifo_self_test(data); + if (fifo_pass) + printk(KERN_INFO "[gyro_self_test] fifo self-test success\n"); + else if (!fifo_pass) + printk(KERN_INFO "[gyro_self_test] fifo self-test fail\n"); + else + printk(KERN_INFO "[gyro_self_test] fifo self-test restry\n"); + + /* bypass self test */ + printk(KERN_INFO "\n[gyro_self_test] bypass self-test\n"); + + bypass_pass = k3g_bypass_self_test(data, NOST, ST); + if (bypass_pass) + printk(KERN_INFO "[gyro_self_test] bypass self-test success\n\n"); + else if (!fifo_pass) + printk(KERN_INFO "[gyro_self_test] bypass self-test fail\n\n"); + else + printk(KERN_INFO "[gyro_self_test] bypass self-test restry\n\n"); + + /* restore backup register */ + for (i = 0; i < 10; i++) { + err = i2c_smbus_write_i2c_block_data(data->client, + CTRL_REG1 | AC, sizeof(backup_regs), + backup_regs); + if (err >= 0) + break; + } + if (err < 0) + pr_err("%s: CTRL_REGs i2c writing failed\n", __func__); + +exit: + if (!data->enable) { + /* If k3g is not enabled, make it go to the power down mode. */ + err = i2c_smbus_write_byte_data(data->client, + CTRL_REG1, 0x00); + if (err < 0) + pr_err("%s: CTRL_REGs i2c writing failed\n", __func__); + } + + if (fifo_pass == 2 && bypass_pass == 2) + printk(KERN_INFO "[gyro_self_test] self-test result : retry\n"); + else + printk(KERN_INFO "[gyro_self_test] self-test result : %s\n", + fifo_pass & bypass_pass ? "pass" : "fail"); + + return sprintf(buf, "%d,%d,%d,%d,%d,%d,%d,%d\n", + NOST[0], NOST[1], NOST[2], ST[0], ST[1], ST[2], + fifo_pass & bypass_pass, fifo_pass); +} + + + +static DEVICE_ATTR(gyro_power_on, 0664, + k3g_power_on, NULL); +static DEVICE_ATTR(gyro_get_temp, 0664, + k3g_get_temp, NULL); +static DEVICE_ATTR(gyro_selftest, 0664, + k3g_self_test, NULL); +static DEVICE_ATTR(gyro_selftest_dps, 0664, + k3g_selftest_dps_show, k3g_selftest_dps_store); +static DEVICE_ATTR(gyro_force_sleep, 0664, + k3g_force_sleep, NULL); + +static const struct file_operations k3g_fops = { + .owner = THIS_MODULE, +}; + +/*************************************************************************/ +/* End of K3G Sysfs */ +/*************************************************************************/ + + +static int k3g_probe(struct i2c_client *client, + const struct i2c_device_id *devid) +{ + int ret; + int err = 0; + struct k3g_data *data; + struct input_dev *input_dev; + + data = kzalloc(sizeof(*data), GFP_KERNEL); + if (data == NULL) { + dev_err(&client->dev, + "failed to allocate memory for module data\n"); + err = -ENOMEM; + goto exit; + } + + data->client = client; + + /* read chip id */ + ret = i2c_smbus_read_byte_data(client, WHO_AM_I); + if (ret != DEVICE_ID) { + if (ret < 0) { + pr_err("%s: i2c for reading chip id failed\n", + __func__); + err = ret; + } else { + pr_err("%s : Device identification failed\n", + __func__); + err = -ENODEV; + } + goto err_read_reg; + } + + mutex_init(&data->lock); + init_completion(&data->data_ready); + + /* allocate gyro input_device */ + input_dev = input_allocate_device(); + if (!input_dev) { + pr_err("%s: could not allocate input device\n", __func__); + err = -ENOMEM; + goto err_input_allocate_device; + } + + data->input_dev = input_dev; + input_set_drvdata(input_dev, data); + input_dev->name = "gyro_sensor"; + /* X */ + input_set_capability(input_dev, EV_REL, REL_RX); + input_set_abs_params(input_dev, REL_RX, -2048, 2047, 0, 0); + /* Y */ + input_set_capability(input_dev, EV_REL, REL_RY); + input_set_abs_params(input_dev, REL_RY, -2048, 2047, 0, 0); + /* Z */ + input_set_capability(input_dev, EV_REL, REL_RZ); + input_set_abs_params(input_dev, REL_RZ, -2048, 2047, 0, 0); + + err = input_register_device(input_dev); + if (err < 0) { + pr_err("%s: could not register input device\n", __func__); + input_free_device(data->input_dev); + goto err_input_register_device; + } + + memcpy(&data->ctrl_regs, &default_ctrl_regs, sizeof(default_ctrl_regs)); + + i2c_set_clientdata(client, data); + dev_set_drvdata(&input_dev->dev, data); + + if (data->client->irq >= 0) { /* interrupt */ + data->interruptible = true; + err = request_threaded_irq(data->client->irq, NULL, + k3g_interrupt_thread, IRQF_TRIGGER_HIGH | IRQF_ONESHOT, + "k3g", data); + if (err < 0) { + pr_err("%s: can't allocate irq.\n", __func__); + goto err_request_irq; + } + disable_irq(data->client->irq); + + } else { /* polling */ + u64 delay_ns; + data->ctrl_regs[2] = 0x00; /* disable interrupt */ + /* hrtimer settings. we poll for gyro values using a timer. */ + hrtimer_init(&data->timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL); + data->polling_delay = ns_to_ktime(200 * NSEC_PER_MSEC); + data->time_to_read = 10000000LL; + delay_ns = ktime_to_ns(data->polling_delay); + do_div(delay_ns, data->time_to_read); + data->entries = delay_ns; + data->timer.function = k3g_timer_func; + + /* the timer just fires off a work queue request. + We need a thread to read i2c (can be slow and blocking). */ + data->k3g_wq = create_singlethread_workqueue("k3g_wq"); + if (!data->k3g_wq) { + err = -ENOMEM; + pr_err("%s: could not create workqueue\n", __func__); + goto err_create_workqueue; + } + /* this is the thread function we run on the work queue */ + INIT_WORK(&data->work, k3g_work_func); + } + + if (device_create_file(&input_dev->dev, + &dev_attr_enable) < 0) { + pr_err("%s: Failed to create device file(%s)!\n", __func__, + dev_attr_enable.attr.name); + goto err_device_create_file; + } + + if (device_create_file(&input_dev->dev, + &dev_attr_poll_delay) < 0) { + pr_err("%s: Failed to create device file(%s)!\n", __func__, + dev_attr_poll_delay.attr.name); + goto err_device_create_file2; + } + + /* register a char dev */ + err = register_chrdev(K3G_MAJOR, "k3g", &k3g_fops); + if (err < 0) { + pr_err("%s: Failed to register chrdev(k3g)\n", __func__); + goto err_register_chrdev; + } + + /* create k3g-dev device class */ + data->k3g_gyro_dev_class = class_create(THIS_MODULE, "K3G_GYRO-dev"); + if (IS_ERR(data->k3g_gyro_dev_class)) { + pr_err("%s: Failed to create class(K3G_GYRO-dev\n", __func__); + err = PTR_ERR(data->k3g_gyro_dev_class); + goto err_class_create; + } + + /* create device node for k3g digital gyroscope */ + data->dev = device_create(data->k3g_gyro_dev_class, NULL, + MKDEV(K3G_MAJOR, 0), NULL, "k3g"); + + if (IS_ERR(data->dev)) { + pr_err("%s: Failed to create device(k3g)\n", __func__); + err = PTR_ERR(data->dev); + goto err_device_create; + } + + if (device_create_file(data->dev, &dev_attr_gyro_power_on) < 0) { + pr_err("%s: Failed to create device file(%s)!\n", __func__, + dev_attr_gyro_power_on.attr.name); + goto device_create_file3; + } + + if (device_create_file(data->dev, &dev_attr_gyro_get_temp) < 0) { + pr_err("%s: Failed to create device file(%s)!\n", __func__, + dev_attr_gyro_get_temp.attr.name); + goto device_create_file4; + } + + if (device_create_file(data->dev, &dev_attr_gyro_selftest) < 0) { + pr_err("%s: Failed to create device file(%s)!\n", __func__, + dev_attr_gyro_selftest.attr.name); + goto device_create_file5; + } + + if (device_create_file(data->dev, &dev_attr_gyro_selftest_dps) < 0) { + pr_err("%s: Failed to create device file(%s)!\n", __func__, + dev_attr_gyro_selftest_dps.attr.name); + goto device_create_file6; + } + + if (device_create_file(data->dev, &dev_attr_gyro_force_sleep) < 0) { + pr_err("%s: Failed to create device file(%s)!\n", __func__, + dev_attr_gyro_force_sleep.attr.name); + goto device_create_file7; + } + + + dev_set_drvdata(data->dev, data); + + return 0; +device_create_file7: + device_remove_file(data->dev, &dev_attr_gyro_force_sleep); +device_create_file6: + device_remove_file(data->dev, &dev_attr_gyro_selftest); +device_create_file5: + device_remove_file(data->dev, &dev_attr_gyro_get_temp); +device_create_file4: + device_remove_file(data->dev, &dev_attr_gyro_power_on); +device_create_file3: + device_destroy(data->k3g_gyro_dev_class, MKDEV(K3G_MAJOR, 0)); +err_device_create: + class_destroy(data->k3g_gyro_dev_class); +err_class_create: + unregister_chrdev(K3G_MAJOR, "k3g"); +err_register_chrdev: + device_remove_file(&input_dev->dev, &dev_attr_poll_delay); +err_device_create_file2: + device_remove_file(&input_dev->dev, &dev_attr_enable); +err_device_create_file: + if (data->interruptible) { + enable_irq(data->client->irq); + free_irq(data->client->irq, data); + } else + destroy_workqueue(data->k3g_wq); + input_unregister_device(data->input_dev); +err_create_workqueue: +err_request_irq: +err_input_register_device: +err_input_allocate_device: + mutex_destroy(&data->lock); +err_read_reg: + kfree(data); +exit: + return err; +} + +static int k3g_remove(struct i2c_client *client) +{ + int err = 0; + struct k3g_data *k3g_data = i2c_get_clientdata(client); + + device_remove_file(k3g_data->dev, &dev_attr_gyro_selftest_dps); + device_remove_file(k3g_data->dev, &dev_attr_gyro_force_sleep); + device_remove_file(k3g_data->dev, &dev_attr_gyro_selftest); + device_remove_file(k3g_data->dev, &dev_attr_gyro_get_temp); + device_remove_file(k3g_data->dev, &dev_attr_gyro_power_on); + device_destroy(k3g_data->k3g_gyro_dev_class, MKDEV(K3G_MAJOR, 0)); + class_destroy(k3g_data->k3g_gyro_dev_class); + unregister_chrdev(K3G_MAJOR, "k3g"); + device_remove_file(&k3g_data->input_dev->dev, &dev_attr_enable); + device_remove_file(&k3g_data->input_dev->dev, &dev_attr_poll_delay); + + if (k3g_data->enable) + err = i2c_smbus_write_byte_data(k3g_data->client, + CTRL_REG1, 0x00); + if (k3g_data->interruptible) { + if (!k3g_data->enable) /* no disable_irq before free_irq */ + enable_irq(k3g_data->client->irq); + free_irq(k3g_data->client->irq, k3g_data); + + } else { + hrtimer_cancel(&k3g_data->timer); + cancel_work_sync(&k3g_data->work); + destroy_workqueue(k3g_data->k3g_wq); + } + + input_unregister_device(k3g_data->input_dev); + mutex_destroy(&k3g_data->lock); + kfree(k3g_data); + + return err; +} + +static int k3g_suspend(struct device *dev) +{ + int err = 0; + struct i2c_client *client = to_i2c_client(dev); + struct k3g_data *k3g_data = i2c_get_clientdata(client); + + if (k3g_data->enable) { + mutex_lock(&k3g_data->lock); + if (!k3g_data->interruptible) { + hrtimer_cancel(&k3g_data->timer); + cancel_work_sync(&k3g_data->work); + } + err = i2c_smbus_write_byte_data(k3g_data->client, + CTRL_REG1, 0x00); + mutex_unlock(&k3g_data->lock); + } + + return err; +} + +static int k3g_resume(struct device *dev) +{ + int err = 0; + struct i2c_client *client = to_i2c_client(dev); + struct k3g_data *k3g_data = i2c_get_clientdata(client); + + if (k3g_data->enable) { + mutex_lock(&k3g_data->lock); + if (!k3g_data->interruptible) + hrtimer_start(&k3g_data->timer, + k3g_data->polling_delay, HRTIMER_MODE_REL); + err = i2c_smbus_write_i2c_block_data(client, + CTRL_REG1 | AC, sizeof(k3g_data->ctrl_regs), + k3g_data->ctrl_regs); + mutex_unlock(&k3g_data->lock); + } + + return err; +} + +static const struct dev_pm_ops k3g_pm_ops = { + .suspend = k3g_suspend, + .resume = k3g_resume +}; + +static const struct i2c_device_id k3g_id[] = { + { "k3g", 0 }, + { } +}; + +MODULE_DEVICE_TABLE(i2c, k3g_id); + +static struct i2c_driver k3g_driver = { + .probe = k3g_probe, + .remove = __devexit_p(k3g_remove), + .id_table = k3g_id, + .driver = { + .pm = &k3g_pm_ops, + .owner = THIS_MODULE, + .name = "k3g" + }, +}; + +static int __init k3g_init(void) +{ + return i2c_add_driver(&k3g_driver); +} + +static void __exit k3g_exit(void) +{ + i2c_del_driver(&k3g_driver); +} + +module_init(k3g_init); +module_exit(k3g_exit); + +MODULE_DESCRIPTION("k3g digital gyroscope driver"); +MODULE_AUTHOR("tim.sk.lee@samsung.com"); +MODULE_LICENSE("GPL"); |