/* * mma7660.c - Linux kernel modules for 3-Axis Orientation/Motion * Detection Sensor * * Copyright (C) 2009-2010 Freescale Semiconductor Ltd. * * 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. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include "../init-input.h" //#include //#include //#ifdef CONFIG_HAS_EARLYSUSPEND //#include //#endif #if defined(CONFIG_HAS_EARLYSUSPEND) || defined(CONFIG_PM) #include #endif /* * Defines */ #define assert(expr)\ if (!(expr)) {\ printk(KERN_ERR "Assertion failed! %s,%d,%s,%s\n",\ __FILE__, __LINE__, __func__, #expr);\ } #define MMA7660_DRV_NAME "mma7660" #define SENSOR_NAME MMA7660_DRV_NAME #define MMA7660_XOUT 0x00 #define MMA7660_YOUT 0x01 #define MMA7660_ZOUT 0x02 #define MMA7660_TILT 0x03 #define MMA7660_SRST 0x04 #define MMA7660_SPCNT 0x05 #define MMA7660_INTSU 0x06 #define MMA7660_MODE 0x07 #define MMA7660_SR 0x08 #define MMA7660_PDET 0x09 #define MMA7660_PD 0x0A #define MMA7660ADDRESS 0x4c #define POLL_INTERVAL_MAX 500 #define POLL_INTERVAL 50 #define INPUT_FUZZ 2 #define INPUT_FLAT 2 #define MK_MMA7660_SR(FILT, AWSR, AMSR)\ (FILT<<5 | AWSR<<3 | AMSR) #define MK_MMA7660_MODE(IAH, IPP, SCPS, ASE, AWE, TON, MODE)\ (IAH<<7 | IPP<<6 | SCPS<<5 | ASE<<4 | AWE<<3 | TON<<2 | MODE) #define MK_MMA7660_INTSU(SHINTX, SHINTY, SHINTZ, GINT, ASINT, PDINT, PLINT, FBINT)\ (SHINTX<<7 | SHINTY<<6 | SHINTZ<<5 | GINT<<4 | ASINT<<3 | PDINT<<2 | PLINT<<1 | FBINT) #define MODE_CHANGE_DELAY_MS 100 static struct device *hwmon_dev; static struct i2c_client *mma7660_i2c_client; static struct mma7660_data_s { struct i2c_client *client; struct input_polled_dev *pollDev; struct mutex interval_mutex; struct mutex init_mutex; #if defined(CONFIG_PM) || defined(CONFIG_HAS_EARLYSUSPEND) volatile int suspend_indator; #endif } mma7660_data; static struct input_polled_dev *mma7660_idev; /* Addresses to scan */ static const unsigned short normal_i2c[2] = {MMA7660ADDRESS, I2C_CLIENT_END}; static __u32 twi_id = 0; static struct sensor_config_info gsensor_info = { .input_type = GSENSOR_TYPE, }; enum { DEBUG_INIT = 1U << 0, DEBUG_CONTROL_INFO = 1U << 1, DEBUG_DATA_INFO = 1U << 2, DEBUG_SUSPEND = 1U << 3, }; static u32 debug_mask = 0; #define dprintk(level_mask, fmt, arg...) if (unlikely(debug_mask & level_mask)) \ printk(KERN_DEBUG fmt , ## arg) module_param_named(debug_mask, debug_mask, int, 0644); static void mma7660_resume_events(struct work_struct *work); static void mma7660_init_events(struct work_struct *work); static struct workqueue_struct *mma7660_resume_wq; static struct workqueue_struct *mma7660_init_wq; static DECLARE_WORK(mma7660_resume_work, mma7660_resume_events); static DECLARE_WORK(mma7660_init_work, mma7660_init_events); //Function as i2c_master_send, and return 1 if operation is successful. static int i2c_write_bytes(struct i2c_client *client, uint8_t *data, uint16_t len) { struct i2c_msg msg; int ret=-1; msg.flags = !I2C_M_RD; msg.addr = client->addr; msg.len = len; msg.buf = data; ret=i2c_transfer(client->adapter, &msg,1); return ret; } static bool gsensor_i2c_test(struct i2c_client * client) { int ret, retry; uint8_t test_data[1] = { 0 }; //only write a data address. for(retry=0; retry < 2; retry++) { ret =i2c_write_bytes(client, test_data, 1); //Test i2c. if (ret == 1) break; msleep(5); } return ret==1 ? true : false; } /** * gsensor_detect - Device detection callback for automatic device creation * return value: * = 0; success; * < 0; err */ static int gsensor_detect(struct i2c_client *client, struct i2c_board_info *info) { struct i2c_adapter *adapter = client->adapter; int ret; dprintk(DEBUG_INIT, "%s enter \n", __func__); ret = i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE_DATA); if (!ret) return -ENODEV; if(twi_id == adapter->nr){ pr_info("%s: addr= %x\n",__func__,client->addr); ret = gsensor_i2c_test(client); if(!ret){ pr_info("%s:I2C connection might be something wrong or maybe the other gsensor equipment! \n",__func__); return -ENODEV; }else{ pr_info("I2C connection sucess!\n"); strlcpy(info->type, SENSOR_NAME, I2C_NAME_SIZE); return 0; } }else{ return -ENODEV; } } static int mma7660_read_xyz(int idx, s8 *pf) { s32 result; assert(mma7660_i2c_client); result=i2c_smbus_read_byte_data(mma7660_i2c_client, idx+MMA7660_XOUT); assert(result>=0); if (result&(1<<6)) { dprintk(DEBUG_DATA_INFO, "mma7660 read new data\n"); return -1; } *pf = (result&(1<<5)) ? (result|(~0x0000003f)) : (result&0x0000003f); return 0; } #if 0 static ssize_t mma7660_delay_show(struct device *dev, struct device_attribute *attr, char *buf) { struct i2c_client *client = mma7660_i2c_client; struct mma7660_data_s *mma7660 = NULL; mma7660 = i2c_get_clientdata(client); return sprintf(buf, "%d\n", mma7660->pollDev->poll_interval); } #endif static ssize_t mma7660_delay_store(struct device *dev,struct device_attribute *attr,const char *buf, size_t count) { unsigned long data; int error; struct i2c_client *client = mma7660_i2c_client; struct mma7660_data_s *mma7660 = NULL; mma7660 = i2c_get_clientdata(client); error = strict_strtoul(buf, 10, &data); if (error) return error; if (data > POLL_INTERVAL_MAX) data = POLL_INTERVAL_MAX; mutex_lock(&mma7660->interval_mutex); mma7660->pollDev->poll_interval = data; mutex_unlock(&mma7660->interval_mutex); return count; } static ssize_t mma7660_value_show(struct device *dev, struct device_attribute *attr, char *buf) { int i; int result = 0; s8 xyz[3]; s16 x, y, z; for (i=0; i<3; i++) { result = mma7660_read_xyz(i, &xyz[i]); if (result < 0) { dprintk(DEBUG_DATA_INFO, "mma7660 read data failed\n"); return 0; } } /* convert signed 8bits to signed 16bits */ x = (((short)xyz[0]) << 8) >> 8; y = (((short)xyz[1]) << 8) >> 8; z = (((short)xyz[2]) << 8) >> 8; return sprintf(buf, "x= %d y= %d z= %d\n", x, y, z); } static ssize_t mma7660_enable_store(struct device *dev,struct device_attribute *attr,const char *buf, size_t count) { unsigned long data; int error; error = strict_strtoul(buf, 10, &data); if (error) { pr_err("%s strict_strtoul error\n", __FUNCTION__); goto exit; } if (data) { mutex_lock(&mma7660_data.init_mutex); error = i2c_smbus_write_byte_data(mma7660_i2c_client, MMA7660_MODE, MK_MMA7660_MODE(0, 1, 0, 0, 0, 0, 1)); mutex_unlock(&mma7660_data.init_mutex); assert(error==0); #if defined(CONFIG_PM) || defined(CONFIG_HAS_EARLYSUSPEND) if (mma7660_data.suspend_indator == 0) #endif mma7660_idev->input->open(mma7660_idev->input); dprintk(DEBUG_CONTROL_INFO, "mma7660 enable setting active \n"); } else { mma7660_idev->input->close(mma7660_idev->input); mutex_lock(&mma7660_data.init_mutex); error = i2c_smbus_write_byte_data(mma7660_i2c_client, MMA7660_MODE, MK_MMA7660_MODE(0, 0, 0, 0, 0, 0, 0)); mutex_unlock(&mma7660_data.init_mutex); assert(error==0); dprintk(DEBUG_CONTROL_INFO, "mma7660 enable setting inactive \n"); } return count; exit: return error; } static DEVICE_ATTR(enable, S_IRUGO|S_IWUSR|S_IWGRP, NULL, mma7660_enable_store); static DEVICE_ATTR(value, S_IRUGO|S_IWUSR|S_IWGRP, mma7660_value_show, NULL); static DEVICE_ATTR(delay, S_IRUGO|S_IWUSR|S_IWGRP, NULL, mma7660_delay_store); static struct attribute *mma7660_attributes[] = { &dev_attr_value.attr, &dev_attr_enable.attr, &dev_attr_delay.attr, NULL }; static struct attribute_group mma7660_attribute_group = { .attrs = mma7660_attributes }; /* * Initialization function */ static int mma7660_init_client(struct i2c_client *client) { int result; mma7660_i2c_client = client; mutex_lock(&mma7660_data.init_mutex); if(0) { /* * Probe the device. We try to set the device to Test Mode and then to * write & verify XYZ registers */ result = i2c_smbus_write_byte_data(client, MMA7660_MODE,MK_MMA7660_MODE(0, 0, 0, 0, 0, 1, 0)); assert(result==0); mdelay(MODE_CHANGE_DELAY_MS); result = i2c_smbus_write_byte_data(client, MMA7660_XOUT, 0x2a); assert(result==0); result = i2c_smbus_write_byte_data(client, MMA7660_YOUT, 0x15); assert(result==0); result = i2c_smbus_write_byte_data(client, MMA7660_ZOUT, 0x3f); assert(result==0); result = i2c_smbus_read_byte_data(client, MMA7660_XOUT); result= i2c_smbus_read_byte_data(client, MMA7660_YOUT); result= i2c_smbus_read_byte_data(client, MMA7660_ZOUT); assert(result=0x3f); } // Enable Orientation Detection Logic result = i2c_smbus_write_byte_data(client, MMA7660_MODE, MK_MMA7660_MODE(0, 0, 0, 0, 0, 0, 0)); //enter standby assert(result==0); result = i2c_smbus_write_byte_data(client, MMA7660_SR, MK_MMA7660_SR(2, 2, 1)); assert(result==0); result = i2c_smbus_write_byte_data(client, MMA7660_INTSU, MK_MMA7660_INTSU(0, 0, 0, 0, 1, 0, 1, 1)); assert(result==0); result = i2c_smbus_write_byte_data(client, MMA7660_SPCNT, 0xA0); assert(result==0); result = i2c_smbus_write_byte_data(client, MMA7660_MODE, MK_MMA7660_MODE(0, 1, 0, 0, 0, 0, 1)); assert(result==0); mutex_unlock(&mma7660_data.init_mutex); mdelay(MODE_CHANGE_DELAY_MS); mma7660_idev->input->open(mma7660_idev->input); return result; } static void report_abs(void) { int i; int result = 0; s8 xyz[3]; s16 x, y, z; for (i=0; i<3; i++) { result = mma7660_read_xyz(i, &xyz[i]); if (result < 0) { dprintk(DEBUG_DATA_INFO, "mma7660 read data failed\n"); return; } } /* convert signed 8bits to signed 16bits */ x = (((short)xyz[0]) << 8) >> 8; y = (((short)xyz[1]) << 8) >> 8; z = (((short)xyz[2]) << 8) >> 8; //pr_info("xyz[0] = 0x%hx, xyz[1] = 0x%hx, xyz[2] = 0x%hx. \n", xyz[0], xyz[1], xyz[2]); //pr_info("x[0] = 0x%hx, y[1] = 0x%hx, z[2] = 0x%hx. \n", x, y, z); input_report_abs(mma7660_idev->input, ABS_X, x); input_report_abs(mma7660_idev->input, ABS_Y, y); input_report_abs(mma7660_idev->input, ABS_Z, z); input_sync(mma7660_idev->input); dprintk(DEBUG_DATA_INFO, "x= 0x%hx, y = 0x%hx, z = 0x%hx\n", x, y, z); } static void mma7660_dev_poll(struct input_polled_dev *dev) { report_abs(); } static void mma7660_init_events (struct work_struct *work) { int result = 0; result = mma7660_init_client(mma7660_i2c_client); assert(result==0); if(result != 0) { printk("<%s> init err !", __func__); return; } dprintk(DEBUG_INIT, "mma7660 init events end\n"); } /* * I2C init/probing/exit functions */ static int mma7660_probe(struct i2c_client *client, const struct i2c_device_id *id) { int result; struct input_dev *idev; struct i2c_adapter *adapter; struct mma7660_data_s* data = &mma7660_data; printk("%s enter\n",__FUNCTION__); dprintk(DEBUG_INIT, "mma7660 probe\n"); mma7660_i2c_client = client; adapter = to_i2c_adapter(client->dev.parent); result = i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE | I2C_FUNC_SMBUS_BYTE_DATA); assert(result); //result = 1; // debug by lchen dprintk(DEBUG_INIT, "<%s> mma7660_init_client result %d\n", __func__, result); hwmon_dev = hwmon_device_register(&client->dev); assert(!(IS_ERR(hwmon_dev))); dev_info(&client->dev, "build time %s %s\n", __DATE__, __TIME__); /*input poll device register */ mma7660_idev = input_allocate_polled_device(); if (!mma7660_idev) { dev_err(&client->dev, "alloc poll device failed!\n"); result = -ENOMEM; return result; } mma7660_idev->poll = mma7660_dev_poll; mma7660_idev->poll_interval = POLL_INTERVAL; mma7660_idev->poll_interval_max = POLL_INTERVAL_MAX; idev = mma7660_idev->input; idev->name = MMA7660_DRV_NAME; idev->id.bustype = BUS_I2C; idev->evbit[0] = BIT_MASK(EV_ABS); mutex_init(&data->interval_mutex); mutex_init(&data->init_mutex); input_set_abs_params(idev, ABS_X, -512, 512, INPUT_FUZZ, INPUT_FLAT); input_set_abs_params(idev, ABS_Y, -512, 512, INPUT_FUZZ, INPUT_FLAT); input_set_abs_params(idev, ABS_Z, -512, 512, INPUT_FUZZ, INPUT_FLAT); result = input_register_polled_device(mma7660_idev); if (result) { dev_err(&client->dev, "register poll device failed!\n"); return result; } mma7660_idev->input->close(mma7660_idev->input); result = sysfs_create_group(&mma7660_idev->input->dev.kobj, &mma7660_attribute_group); //result = device_create_file(&mma7660_idev->input->dev, &dev_attr_enable); //result = device_create_file(&mma7660_idev->input->dev, &dev_attr_value); if(result) { dev_err(&client->dev, "create sys failed\n"); } data->client = client; data->pollDev = mma7660_idev; i2c_set_clientdata(client, data); mma7660_init_wq = create_singlethread_workqueue("mma7660_init"); if (mma7660_init_wq == NULL) { printk("create mma7660_init_wq fail!\n"); return -ENOMEM; } /* Initialize the MMA7660 chip */ queue_work(mma7660_init_wq, &mma7660_init_work); mma7660_resume_wq = create_singlethread_workqueue("mma7660_resume"); if (mma7660_resume_wq == NULL) { printk("create mma7660_resume_wq fail!\n"); return -ENOMEM; } #ifdef CONFIG_HAS_EARLYSUSPEND mma7660_data.early_suspend.level = EARLY_SUSPEND_LEVEL_BLANK_SCREEN + 1; mma7660_data.early_suspend.suspend = mma7660_early_suspend; mma7660_data.early_suspend.resume = mma7660_late_resume; register_early_suspend(&mma7660_data.early_suspend); mma7660_data.suspend_indator = 0; #endif dprintk(DEBUG_INIT, "mma7660 probe end\n"); return result; } static int mma7660_remove(struct i2c_client *client) { int result; mutex_lock(&mma7660_data.init_mutex); result = i2c_smbus_write_byte_data(client,MMA7660_MODE, MK_MMA7660_MODE(0, 0, 0, 0, 0, 0, 0)); assert(result==0); mutex_unlock(&mma7660_data.init_mutex); #ifdef CONFIG_HAS_EARLYSUSPEND unregister_early_suspend(&mma7660_data.early_suspend); #endif cancel_work_sync(&mma7660_resume_work); destroy_workqueue(mma7660_resume_wq); sysfs_remove_group(&mma7660_idev->input->dev.kobj, &mma7660_attribute_group); mma7660_idev->input->close(mma7660_idev->input); input_unregister_polled_device(mma7660_idev); input_free_polled_device(mma7660_idev); hwmon_device_unregister(hwmon_dev); cancel_work_sync(&mma7660_init_work); destroy_workqueue(mma7660_init_wq); i2c_set_clientdata(client, NULL); return result; } static void mma7660_resume_events (struct work_struct *work) { #if defined(CONFIG_HAS_EARLYSUSPEND) || defined(CONFIG_PM) int result = 0; dprintk(DEBUG_INIT, "mma7660 device init\n"); result = mma7660_init_client(mma7660_i2c_client); assert(result==0); mutex_lock(&mma7660_data.init_mutex); mma7660_data.suspend_indator = 0; result = i2c_smbus_write_byte_data(mma7660_i2c_client, MMA7660_MODE, MK_MMA7660_MODE(0, 1, 0, 0, 0, 0, 1)); mutex_unlock(&mma7660_data.init_mutex); assert(result==0); mma7660_idev->input->open(mma7660_idev->input); dprintk(DEBUG_INIT, "mma7660 device init end\n"); return; #endif } #ifdef CONFIG_PM static int mma7660_resume(struct i2c_client *client) { int result = 0; dprintk(DEBUG_SUSPEND, "mma7660 resume\n"); if (SUPER_STANDBY == standby_type) { queue_work(mma7660_resume_wq, &mma7660_resume_work); } if (SUPER_STANDBY == standby_type) { mutex_lock(&mma7660_data.init_mutex); mma7660_data.suspend_indator = 0; result = i2c_smbus_write_byte_data(mma7660_i2c_client, MMA7660_MODE, MK_MMA7660_MODE(0, 1, 0, 0, 0, 0, 1)); mutex_unlock(&mma7660_data.init_mutex); assert(result==0); mma7660_idev->input->open(mma7660_idev->input); } return result; } static int mma7660_suspend(struct i2c_client *client, pm_message_t mesg) { int result; dprintk(DEBUG_SUSPEND, "mma7660 suspend\n"); flush_workqueue(mma7660_resume_wq); mma7660_idev->input->close(mma7660_idev->input); mutex_lock(&mma7660_data.init_mutex); mma7660_data.suspend_indator = 1; result = i2c_smbus_write_byte_data(mma7660_i2c_client, MMA7660_MODE, MK_MMA7660_MODE(0, 0, 0, 0, 0, 0, 0)); mutex_unlock(&mma7660_data.init_mutex); assert(result==0); return result; } #endif /* CONFIG_HAS_EARLYSUSPEND */ static const struct i2c_device_id mma7660_id[] = { { MMA7660_DRV_NAME, 1 }, { } }; MODULE_DEVICE_TABLE(i2c, mma7660_id); static struct i2c_driver mma7660_driver = { .class = I2C_CLASS_HWMON, .driver = { .name = MMA7660_DRV_NAME, .owner = THIS_MODULE, .of_match_table = "allwinner,sun50i-gsensor-para", }, .probe = mma7660_probe, .remove = mma7660_remove, #ifdef CONFIG_HAS_EARLYSUSPEND #else #ifdef CONFIG_PM .suspend = mma7660_suspend, .resume = mma7660_resume, #endif #endif .id_table = mma7660_id, .detect = gsensor_detect, .address_list = normal_i2c, }; static int __init mma7660_init(void) { int ret = -1; printk("======%s=========. \n", __func__); if (input_fetch_sysconfig_para(&(gsensor_info.input_type))) { printk("%s: err.\n", __func__); return -1; } else{ ret = input_init_platform_resource(&(gsensor_info.input_type)); if (0 != ret){ printk("%s:ctp_ops.init_platform_resource err. \n", __func__); } } twi_id = gsensor_info.twi_id; dprintk(DEBUG_INIT, "%s i2c twi is %d \n", __func__, twi_id); input_set_power_enable(&(gsensor_info.input_type),1); ret = i2c_add_driver(&mma7660_driver); if (ret < 0) { printk(KERN_INFO "add mma7660 i2c driver failed\n"); return -ENODEV; } dprintk(DEBUG_INIT, "add mma7660 i2c driver\n"); return ret; } static void __exit mma7660_exit(void) { printk(KERN_INFO "remove mma7660 i2c driver.\n"); i2c_del_driver(&mma7660_driver); input_set_power_enable(&(gsensor_info.input_type),0); } MODULE_AUTHOR("Chen Gang "); MODULE_DESCRIPTION("MMA7660 3-Axis Orientation/Motion Detection Sensor driver"); MODULE_LICENSE("GPL"); MODULE_VERSION("1.1"); module_init(mma7660_init); module_exit(mma7660_exit);