diff options
Diffstat (limited to 'drivers/sensor/ak8963.c')
-rw-r--r-- | drivers/sensor/ak8963.c | 880 |
1 files changed, 880 insertions, 0 deletions
diff --git a/drivers/sensor/ak8963.c b/drivers/sensor/ak8963.c new file mode 100644 index 0000000..674deee --- /dev/null +++ b/drivers/sensor/ak8963.c @@ -0,0 +1,880 @@ +/* + * 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/i2c.h> +#include <linux/interrupt.h> +#include <linux/irq.h> +#include <linux/slab.h> +#include <linux/gpio.h> +#include <linux/miscdevice.h> +#include <linux/uaccess.h> +#include <linux/delay.h> +#include <linux/fs.h> +#include <linux/sensor/ak8963.h> +#include <linux/completion.h> +#include "ak8963-reg.h" +#include <linux/sensor/sensors_core.h> + +#if defined(CONFIG_SLP) || defined(CONFIG_MACH_GC1) +#define FACTORY_TEST +#else +#undef FACTORY_TEST +#endif +#undef MAGNETIC_LOGGING + +#define VENDOR "AKM" +#define CHIP_ID "AK8963C" + + +struct akm8963_data { + struct i2c_client *this_client; + struct akm8963_platform_data *pdata; + struct mutex lock; + struct miscdevice akmd_device; + struct completion data_ready; + struct device *dev; + wait_queue_head_t state_wq; + u8 asa[3]; + int irq; +}; + + +static s32 akm8963_ecs_set_mode_power_down(struct akm8963_data *akm) +{ + s32 ret; + ret = i2c_smbus_write_byte_data(akm->this_client, + AK8963_REG_CNTL1, AK8963_CNTL1_POWER_DOWN); + return ret; +} + +static int akm8963_ecs_set_mode(struct akm8963_data *akm, char mode) +{ + s32 ret; + + switch (mode) { + case AK8963_CNTL1_SNG_MEASURE: + ret = i2c_smbus_write_byte_data(akm->this_client, + AK8963_REG_CNTL1, AK8963_CNTL1_SNG_MEASURE); + break; + case AK8963_CNTL1_FUSE_ACCESS: + ret = i2c_smbus_write_byte_data(akm->this_client, + AK8963_REG_CNTL1, AK8963_CNTL1_FUSE_ACCESS); + break; + case AK8963_CNTL1_POWER_DOWN: + ret = akm8963_ecs_set_mode_power_down(akm); + break; + case AK8963_CNTL1_SELF_TEST: + ret = i2c_smbus_write_byte_data(akm->this_client, + AK8963_REG_CNTL1, AK8963_CNTL1_SELF_TEST); + break; + default: + return -EINVAL; + } + + if (ret < 0) + return ret; + + /* Wait at least 300us after changing mode. */ + udelay(300); + + return 0; +} + +static int akm8963_Reset(struct akm8963_data *akm) +{ + int err = 0; + + gpio_set_value(GPIO_MSENSE_RST_N, 0); + udelay(5); + gpio_set_value(GPIO_MSENSE_RST_N, 1); + /* Device will be accessible 100 us after */ + udelay(100); + + return err; +} + +static int akmd_copy_in(unsigned int cmd, void __user *argp, + void *buf, size_t buf_size) +{ + if (!(cmd & IOC_IN)) + return 0; + if (_IOC_SIZE(cmd) > buf_size) + return -EINVAL; + if (copy_from_user(buf, argp, _IOC_SIZE(cmd))) + return -EFAULT; + return 0; +} + +static int akmd_copy_out(unsigned int cmd, void __user *argp, + void *buf, size_t buf_size) +{ + if (!(cmd & IOC_OUT)) + return 0; + if (_IOC_SIZE(cmd) > buf_size) + return -EINVAL; + if (copy_to_user(argp, buf, _IOC_SIZE(cmd))) + return -EFAULT; + return 0; +} + +static void akm8963_disable_irq(struct akm8963_data *akm) +{ + disable_irq(akm->irq); + if (try_wait_for_completion(&akm->data_ready)) { + /* we actually got the interrupt before we could disable it + * so we need to enable again to undo our disable since the + * irq_handler already disabled it + */ + enable_irq(akm->irq); + } +} + +static irqreturn_t akm8963_irq_handler(int irq, void *data) +{ + struct akm8963_data *akm = data; + disable_irq_nosync(irq); + complete(&akm->data_ready); + return IRQ_HANDLED; +} + +static int akm8963_wait_for_data_ready(struct akm8963_data *akm) +{ + int data_ready = gpio_get_value(akm->pdata->gpio_data_ready_int); + int err; + + if (data_ready) + return 0; + + enable_irq(akm->irq); + + err = wait_for_completion_timeout(&akm->data_ready, 2*HZ); + if (err > 0) + return 0; + + akm8963_disable_irq(akm); + + if (err == 0) { + pr_err("akm: wait timed out\n"); + return -ETIMEDOUT; + } + + pr_err("akm: wait restart\n"); + return err; +} + +static ssize_t akmd_read(struct file *file, char __user *buf, + size_t count, loff_t *pos) +{ + struct akm8963_data *akm = container_of(file->private_data, + struct akm8963_data, akmd_device); + short x = 0, y = 0, z = 0; + int ret; + u8 data[8]; + + mutex_lock(&akm->lock); + ret = akm8963_ecs_set_mode(akm, AK8963_CNTL1_SNG_MEASURE); + if (ret) { + mutex_unlock(&akm->lock); + goto done; + } + ret = akm8963_wait_for_data_ready(akm); + if (ret) { + mutex_unlock(&akm->lock); + goto done; + } + ret = i2c_smbus_read_i2c_block_data(akm->this_client, AK8963_REG_ST1, + sizeof(data), data); + mutex_unlock(&akm->lock); + + if (ret != sizeof(data)) { + pr_err("%s: failed to read %d bytes of mag data\n", + __func__, sizeof(data)); + goto done; + } + + if (data[0] & 0x01) { + x = (data[2] << 8) + data[1]; + y = (data[4] << 8) + data[3]; + z = (data[6] << 8) + data[5]; + } else + pr_err("%s: invalid raw data(st1 = %d)\n", + __func__, data[0] & 0x01); + +done: + return sprintf(buf, "%d,%d,%d\n", x, y, z); +} + +static long akmd_ioctl(struct file *file, unsigned int cmd, + unsigned long arg) +{ + void __user *argp = (void __user *)arg; + struct akm8963_data *akm = container_of(file->private_data, + struct akm8963_data, akmd_device); + int ret; + #ifdef MAGNETIC_LOGGING + short x, y, z; + #endif + union { + char raw[RWBUF_SIZE]; + int status; + char mode; + u8 data[8]; + } rwbuf; + + ret = akmd_copy_in(cmd, argp, rwbuf.raw, sizeof(rwbuf)); + if (ret) + return ret; + + switch (cmd) { + case ECS_IOCTL_WRITE: + if ((rwbuf.raw[0] < 2) || (rwbuf.raw[0] > (RWBUF_SIZE - 1))) + return -EINVAL; + if (copy_from_user(&rwbuf.raw[2], argp+2, rwbuf.raw[0]-1)) + return -EFAULT; + + ret = i2c_smbus_write_i2c_block_data(akm->this_client, + rwbuf.raw[1], + rwbuf.raw[0] - 1, + &rwbuf.raw[2]); + break; + case ECS_IOCTL_READ: + if ((rwbuf.raw[0] < 1) || (rwbuf.raw[0] > (RWBUF_SIZE - 1))) + return -EINVAL; + + ret = i2c_smbus_read_i2c_block_data(akm->this_client, + rwbuf.raw[1], + rwbuf.raw[0], + &rwbuf.raw[1]); + if (ret < 0) + return ret; + if (copy_to_user(argp+1, rwbuf.raw+1, rwbuf.raw[0])) + return -EFAULT; + return 0; + case ECS_IOCTL_SET_MODE: + mutex_lock(&akm->lock); + ret = akm8963_ecs_set_mode(akm, rwbuf.mode); + mutex_unlock(&akm->lock); + break; + case ECS_IOCTL_RESET: + akm8963_Reset(akm); + break; + case ECS_IOCTL_GETDATA: + mutex_lock(&akm->lock); + ret = akm8963_wait_for_data_ready(akm); + if (ret) { + mutex_unlock(&akm->lock); + return ret; + } + ret = i2c_smbus_read_i2c_block_data(akm->this_client, + AK8963_REG_ST1, + sizeof(rwbuf.data), + rwbuf.data); + + #ifdef MAGNETIC_LOGGING + x = (rwbuf.data[2] << 8) + rwbuf.data[1]; + y = (rwbuf.data[4] << 8) + rwbuf.data[3]; + z = (rwbuf.data[6] << 8) + rwbuf.data[5]; + + pr_info("%s:ST1=%d, x=%d, y=%d, z=%d, ST2=%d\n", + __func__, rwbuf.data[0], x, y, z, rwbuf.data[7]); + #endif + + mutex_unlock(&akm->lock); + if (ret != sizeof(rwbuf.data)) { + pr_err("%s : failed to read %d bytes of mag data\n", + __func__, sizeof(rwbuf.data)); + return -EIO; + } + break; + default: + return -ENOTTY; + } + + if (ret < 0) + return ret; + + return akmd_copy_out(cmd, argp, rwbuf.raw, sizeof(rwbuf)); +} + +static const struct file_operations akmd_fops = { + .owner = THIS_MODULE, + .open = nonseekable_open, + .read = akmd_read, + .unlocked_ioctl = akmd_ioctl, +}; + +static int akm8963_setup_irq(struct akm8963_data *akm) +{ + int rc = -EIO; + struct akm8963_platform_data *pdata = akm->pdata; + int irq; + + if (akm->this_client->irq) + irq = akm->this_client->irq; + else { + rc = gpio_request(pdata->gpio_data_ready_int, "gpio_akm_int"); + if (rc < 0) { + pr_err("%s: gpio %d request failed (%d)\n", + __func__, pdata->gpio_data_ready_int, rc); + return rc; + } + + rc = gpio_direction_input(pdata->gpio_data_ready_int); + if (rc < 0) { + pr_err("%s: failed to set gpio %d as input (%d)\n", + __func__, pdata->gpio_data_ready_int, rc); + goto err_request_irq; + } + + irq = gpio_to_irq(pdata->gpio_data_ready_int); + } + /* trigger high so we don't miss initial interrupt if it + * is already pending + */ + rc = request_irq(irq, akm8963_irq_handler, + IRQF_TRIGGER_RISING | IRQF_ONESHOT, "akm_int", akm); + if (rc < 0) { + pr_err("%s: request_irq(%d) failed for gpio %d (%d)\n", + __func__, irq, + pdata->gpio_data_ready_int, rc); + goto err_request_irq; + } + + /* start with interrupt disabled until the driver is enabled */ + akm->irq = irq; + akm8963_disable_irq(akm); + + goto done; + +err_request_irq: + gpio_free(pdata->gpio_data_ready_int); +done: + return rc; +} + +#ifdef FACTORY_TEST +static int ak8963c_selftest(struct akm8963_data *ak_data, int *sf) +{ + u8 buf[6]; + s16 x, y, z; + + /* read device info */ + i2c_smbus_read_i2c_block_data(ak_data->this_client, + AK8963_REG_WIA, 2, buf); + pr_info("%s: device id = 0x%x, info = 0x%x\n", + __func__, buf[0], buf[1]); + + /* set ATSC self test bit to 1 */ + i2c_smbus_write_byte_data(ak_data->this_client, + AK8963_REG_ASTC, 0x40); + + /* start self test */ + i2c_smbus_write_byte_data(ak_data->this_client, + AK8963_REG_CNTL1, + AK8963_CNTL1_SELF_TEST); + + /* wait for data ready */ + while (1) { + msleep(20); + if (i2c_smbus_read_byte_data(ak_data->this_client, + AK8963_REG_ST1) == 1) { + break; + } + } + + i2c_smbus_read_i2c_block_data(ak_data->this_client, + AK8963_REG_HXL, sizeof(buf), buf); + + /* set ATSC self test bit to 0 */ + i2c_smbus_write_byte_data(ak_data->this_client, + AK8963_REG_ASTC, 0x00); + + x = buf[0] | (buf[1] << 8); + y = buf[2] | (buf[3] << 8); + z = buf[4] | (buf[5] << 8); + + /* Hadj = (H*(Asa+128))/256 */ + x = (x*(ak_data->asa[0] + 128)) >> 8; + y = (y*(ak_data->asa[1] + 128)) >> 8; + z = (z*(ak_data->asa[2] + 128)) >> 8; + + pr_info("%s: self test x = %d, y = %d, z = %d\n", + __func__, x, y, z); + if ((x >= -200) && (x <= 200)) + pr_info("%s: x passed self test, expect -200<=x<=200\n", + __func__); + else + pr_info("%s: x failed self test, expect -200<=x<=200\n", + __func__); + if ((y >= -200) && (y <= 200)) + pr_info("%s: y passed self test, expect -200<=y<=200\n", + __func__); + else + pr_info("%s: y failed self test, expect -200<=y<=200\n", + __func__); + if ((z >= -3200) && (z <= -800)) + pr_info("%s: z passed self test, expect -3200<=z<=-800\n", + __func__); + else + pr_info("%s: z failed self test, expect -3200<=z<=-800\n", + __func__); + + sf[0] = x; + sf[1] = y; + sf[2] = z; + + if (((x >= -200) && (x <= 200)) && + ((y >= -200) && (y <= 200)) && + ((z >= -3200) && (z <= -800))) + return 1; + else + return 0; +} + +static ssize_t ak8963c_get_asa(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct akm8963_data *ak_data = dev_get_drvdata(dev); + + return sprintf(buf, "%d, %d, %d\n", ak_data->asa[0], + ak_data->asa[1], ak_data->asa[2]); +} + +static ssize_t ak8963c_get_selftest(struct device *dev, + struct device_attribute *attr, char *buf) +{ + int ret = 0; + int sf[3] = {0,}; + + ret = ak8963c_selftest(dev_get_drvdata(dev), sf); + return sprintf(buf, "%d, %d, %d, %d\n", ret, sf[0], sf[1], sf[2]); +} + +static ssize_t ak8963c_check_registers(struct device *dev, + struct device_attribute *attr, char *strbuf) +{ + struct akm8963_data *ak_data = dev_get_drvdata(dev); + u8 buf[13]; + + /* power down */ + i2c_smbus_write_byte_data(ak_data->this_client, + AK8963_REG_CNTL1, AK8963_CNTL1_POWER_DOWN); + + /* get the value */ + i2c_smbus_read_i2c_block_data(ak_data->this_client, + AK8963_REG_WIA, 11, buf); + + buf[11] = i2c_smbus_read_byte_data(ak_data->this_client, + AK8963_REG_ASTC); + buf[12] = i2c_smbus_read_byte_data(ak_data->this_client, + AK8963_REG_I2CDIS); + + + return sprintf(strbuf, "%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d\n", + buf[0], buf[1], buf[2], buf[3], buf[4], buf[5], + buf[6], buf[7], buf[8], buf[9], buf[10], buf[11], + buf[12]); +} + +static ssize_t ak8963c_check_cntl(struct device *dev, + struct device_attribute *attr, char *strbuf) +{ + struct akm8963_data *ak_data = dev_get_drvdata(dev); + u8 buf; + int err; + + /* power down */ + err = i2c_smbus_write_byte_data(ak_data->this_client, + AK8963_REG_CNTL1, AK8963_CNTL1_POWER_DOWN); + + buf = i2c_smbus_read_byte_data(ak_data->this_client, + AK8963_REG_CNTL1); + + return sprintf(strbuf, "%s\n", + ((buf == AK8963_CNTL1_POWER_DOWN) ? "OK" : "NG")); +} + +static ssize_t ak8963c_get_status(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct akm8963_data *ak_data = dev_get_drvdata(dev); + int success; + + if ((ak_data->asa[0] == 0) | (ak_data->asa[0] == 0xff) + | (ak_data->asa[1] == 0) | (ak_data->asa[1] == 0xff) + | (ak_data->asa[2] == 0) | (ak_data->asa[2] == 0xff)) + success = 0; + else + success = 1; + + return sprintf(buf, "%s\n", (success ? "OK" : "NG")); +} + +static ssize_t ak8963_adc(struct device *dev, + struct device_attribute *attr, char *strbuf) +{ + struct akm8963_data *ak_data = dev_get_drvdata(dev); + u8 buf[8]; + s16 x, y, z; + int err, success; + + /* start ADC conversion */ + err = i2c_smbus_write_byte_data(ak_data->this_client, + AK8963_REG_CNTL1, AK8963_CNTL1_SNG_MEASURE); + + /* wait for ADC conversion to complete */ + err = akm8963_wait_for_data_ready(ak_data); + if (err) { + pr_err("%s: wait for data ready failed\n", __func__); + return err; + } + msleep(20); + /* get the value and report it */ + err = i2c_smbus_read_i2c_block_data(ak_data->this_client, + AK8963_REG_ST1, sizeof(buf), buf); + if (err != sizeof(buf)) { + pr_err("%s: read data over i2c failed\n", __func__); + return err; + } + + /* buf[0] is status1, buf[7] is status2 */ + if ((buf[0] == 0) | (buf[7] == 1)) + success = 0; + else + success = 1; + + x = buf[1] | (buf[2] << 8); + y = buf[3] | (buf[4] << 8); + z = buf[5] | (buf[6] << 8); + + pr_info("raw x = %d, y = %d, z = %d\n", x, y, z); + return sprintf(strbuf, + "%s, %d, %d, %d\n", (success ? "OK" : "NG"), x, y, z); +} +#endif + +static ssize_t ak8963_show_raw_data(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct akm8963_data *akm = dev_get_drvdata(dev); + short x = 0, y = 0, z = 0; + int ret; + u8 data[8] = {0,}; + + mutex_lock(&akm->lock); + ret = akm8963_ecs_set_mode(akm, AK8963_CNTL1_SNG_MEASURE); + if (ret) { + mutex_unlock(&akm->lock); + goto done; + } + ret = akm8963_wait_for_data_ready(akm); + if (ret) { + mutex_unlock(&akm->lock); + goto done; + } + ret = i2c_smbus_read_i2c_block_data(akm->this_client, AK8963_REG_ST1, + sizeof(data), data); + mutex_unlock(&akm->lock); + + if (ret != sizeof(data)) { + pr_err("%s: failed to read %d bytes of mag data\n", + __func__, sizeof(data)); + goto done; + } + + if (data[0] & 0x01) { + x = (data[2] << 8) + data[1]; + y = (data[4] << 8) + data[3]; + z = (data[6] << 8) + data[5]; + } else + pr_err("%s: invalid raw data(st1 = %d)\n", + __func__, data[0] & 0x01); + +done: + return sprintf(buf, "%d,%d,%d\n", x, y, z); +} + +static ssize_t ak8963_show_vendor(struct device *dev, + struct device_attribute *attr, char *buf) +{ + return sprintf(buf, "%s\n", VENDOR); +} + +static ssize_t ak8963_show_name(struct device *dev, + struct device_attribute *attr, char *buf) +{ + return sprintf(buf, "%s\n", CHIP_ID); +} + +static DEVICE_ATTR(raw_data, 0664, + ak8963_show_raw_data, NULL); +static DEVICE_ATTR(vendor, 0664, + ak8963_show_vendor, NULL); +static DEVICE_ATTR(name, 0664, + ak8963_show_name, NULL); + +#ifdef FACTORY_TEST +static DEVICE_ATTR(asa, 0664, + ak8963c_get_asa, NULL); +static DEVICE_ATTR(selftest, 0664, + ak8963c_get_selftest, NULL); +static DEVICE_ATTR(chk_registers, 0664, + ak8963c_check_registers, NULL); +static DEVICE_ATTR(dac, 0664, + ak8963c_check_cntl, NULL); +static DEVICE_ATTR(status, 0664, + ak8963c_get_status, NULL); +static DEVICE_ATTR(adc, 0664, + ak8963_adc, NULL); +#endif + +int akm8963_probe(struct i2c_client *client, + const struct i2c_device_id *devid) +{ + struct akm8963_data *akm; + int err; + + pr_info("%s is called.\n", __func__); + if (client->dev.platform_data == NULL && client->irq == 0) { + dev_err(&client->dev, "platform data & irq are NULL.\n"); + err = -ENODEV; + goto exit_platform_data_null; + } + + if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) { + dev_err(&client->dev, "I2C check failed, exiting.\n"); + err = -ENODEV; + goto exit_check_functionality_failed; + } + + akm = kzalloc(sizeof(struct akm8963_data), GFP_KERNEL); + if (!akm) { + dev_err(&client->dev, + "failed to allocate memory for module data\n"); + err = -ENOMEM; + goto exit_alloc_data_failed; + } + + akm->pdata = client->dev.platform_data; + mutex_init(&akm->lock); + init_completion(&akm->data_ready); + + i2c_set_clientdata(client, akm); + akm->this_client = client; + + err = akm8963_ecs_set_mode_power_down(akm); + if (err < 0) { + pr_err("%s: akm8963_ecs_set_mode_power_down fail(err=%d)\n", + __func__, err); + goto exit_set_mode_power_down_failed; + } + + err = akm8963_setup_irq(akm); + if (err) { + pr_err("%s: could not setup irq\n", __func__); + goto exit_setup_irq; + } + + akm->akmd_device.minor = MISC_DYNAMIC_MINOR; + akm->akmd_device.name = "akm8963"; + akm->akmd_device.fops = &akmd_fops; + + err = misc_register(&akm->akmd_device); + if (err) { + pr_err("%s, misc_register failed.\n", __func__); + goto exit_akmd_device_register_failed; + } + + init_waitqueue_head(&akm->state_wq); + + /* put into fuse access mode to read asa data */ + err = i2c_smbus_write_byte_data(client, AK8963_REG_CNTL1, + AK8963_CNTL1_FUSE_ACCESS); + if (err) { + pr_err("%s: unable to enter fuse rom mode\n", __func__); + goto exit_i2c_failed; + } + + err = i2c_smbus_read_i2c_block_data(client, AK8963_FUSE_ASAX, + sizeof(akm->asa), akm->asa); + if (err != sizeof(akm->asa)) { + pr_err("%s: unable to load factory sensitivity adjust values\n", + __func__); + goto exit_i2c_failed; + } else + pr_info("%s: asa_x = %d, asa_y = %d, asa_z = %d\n", __func__, + akm->asa[0], akm->asa[1], akm->asa[2]); + + err = i2c_smbus_write_byte_data(client, AK8963_REG_CNTL1, + AK8963_CNTL1_POWER_DOWN); + if (err) { + dev_err(&client->dev, "Error in setting power down mode\n"); + goto exit_i2c_failed; + } + + akm->dev = sensors_classdev_register("magnetic_sensor"); + if (IS_ERR(akm->dev)) { + pr_err("Failed to create device!"); + goto exit_class_create_failed; + } + + if (device_create_file(akm->dev, &dev_attr_raw_data) < 0) { + pr_err("Failed to create device file(%s)!\n", + dev_attr_raw_data.attr.name); + goto exit_device_create_raw_data; + } + + if (device_create_file(akm->dev, &dev_attr_vendor) < 0) { + pr_err("Failed to create device file(%s)!\n", + dev_attr_name.attr.name); + goto exit_device_create_vendor; + } + + if (device_create_file(akm->dev, &dev_attr_name) < 0) { + pr_err("Failed to create device file(%s)!\n", + dev_attr_raw_data.attr.name); + goto exit_device_create_name; + } + +#ifdef FACTORY_TEST + if (device_create_file(akm->dev, &dev_attr_adc) < 0) { + pr_err("Failed to create device file(%s)!\n", + dev_attr_adc.attr.name); + goto exit_device_create_file1; + } + + if (device_create_file(akm->dev, &dev_attr_status) < 0) { + pr_err("Failed to create device file(%s)!\n", + dev_attr_status.attr.name); + goto exit_device_create_file2; + } + + if (device_create_file(akm->dev, &dev_attr_asa) < 0) { + pr_err("Failed to create device file(%s)!\n", + dev_attr_asa.attr.name); + goto exit_device_create_file3; + } + if (device_create_file(akm->dev, &dev_attr_selftest) < 0) { + pr_err("Failed to create device file(%s)!\n", + dev_attr_selftest.attr.name); + goto exit_device_create_file4; + } + if (device_create_file(akm->dev, + &dev_attr_chk_registers) < 0) { + pr_err("Failed to create device file(%s)!\n", + dev_attr_chk_registers.attr.name); + goto exit_device_create_file5; + } + if (device_create_file(akm->dev, &dev_attr_dac) < 0) { + pr_err("Failed to create device file(%s)!\n", + dev_attr_dac.attr.name); + goto exit_device_create_file6; + } +#endif + dev_set_drvdata(akm->dev, akm); + +pr_info("%s is successful.\n", __func__); +return 0; + +#ifdef FACTORY_TEST +exit_device_create_file6: + device_remove_file(akm->dev, &dev_attr_chk_registers); +exit_device_create_file5: + device_remove_file(akm->dev, &dev_attr_selftest); +exit_device_create_file4: + device_remove_file(akm->dev, &dev_attr_asa); +exit_device_create_file3: + device_remove_file(akm->dev, &dev_attr_status); +exit_device_create_file2: + device_remove_file(akm->dev, &dev_attr_adc); +exit_device_create_file1: + device_remove_file(akm->dev, &dev_attr_name); +#endif +exit_device_create_name: + device_remove_file(akm->dev, &dev_attr_vendor); +exit_device_create_vendor: + device_remove_file(akm->dev, &dev_attr_raw_data); +exit_device_create_raw_data: + sensors_classdev_unregister(akm->dev); +exit_class_create_failed: +exit_i2c_failed: + misc_deregister(&akm->akmd_device); +exit_akmd_device_register_failed: + free_irq(akm->irq, akm); + gpio_free(akm->pdata->gpio_data_ready_int); +exit_setup_irq: +exit_set_mode_power_down_failed: + mutex_destroy(&akm->lock); + kfree(akm); +exit_alloc_data_failed: +exit_check_functionality_failed: +exit_platform_data_null: + return err; +} + +static int __devexit akm8963_remove(struct i2c_client *client) +{ + struct akm8963_data *akm = i2c_get_clientdata(client); + + #ifdef FACTORY_TEST + device_remove_file(akm->dev, &dev_attr_adc); + device_remove_file(akm->dev, &dev_attr_status); + device_remove_file(akm->dev, &dev_attr_asa); + device_remove_file(akm->dev, &dev_attr_selftest); + device_remove_file(akm->dev, &dev_attr_chk_registers); + device_remove_file(akm->dev, &dev_attr_dac); + #endif + device_remove_file(akm->dev, &dev_attr_name); + device_remove_file(akm->dev, &dev_attr_vendor); + device_remove_file(akm->dev, &dev_attr_raw_data); + sensors_classdev_unregister(akm->dev); + misc_deregister(&akm->akmd_device); + free_irq(akm->irq, akm); + gpio_free(akm->pdata->gpio_data_ready_int); + mutex_destroy(&akm->lock); + kfree(akm); + return 0; +} + +static const struct i2c_device_id akm8963_id[] = { + {AKM8963_I2C_NAME, 0 }, + { } +}; + +static struct i2c_driver akm8963_driver = { + .probe = akm8963_probe, + .remove = akm8963_remove, + .id_table = akm8963_id, + .driver = { + .name = AKM8963_I2C_NAME, + }, +}; + +static int __init akm8963_init(void) +{ + return i2c_add_driver(&akm8963_driver); +} + +static void __exit akm8963_exit(void) +{ + i2c_del_driver(&akm8963_driver); +} + +module_init(akm8963_init); +module_exit(akm8963_exit); + +MODULE_DESCRIPTION("AKM8963 compass driver"); +MODULE_AUTHOR("Samsung Electronics"); +MODULE_LICENSE("GPL"); |