泛泰A860 PIZEO /TS5000协议/TI drv2665芯片 Android内核震动驱动源码
来源:互联网 发布:流体计算软件app 编辑:程序博客网 时间:2024/06/06 08:47
/* * ti_drv2665.c - Texas Instruments DRV2665 piezo haptic driver * * Copyright (C) 2011 Texas Instruments * * Contact: Ameya Palande <ameya.palande@ti.com> * * 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 <linux/module.h>#include <linux/slab.h>#include <linux/i2c.h>#include <linux/pm.h>#include <linux/delay.h>#include <linux/regulator/consumer.h>#include <linux/gpio.h>#include <linux/fs.h>#include "../staging/android/timed_output.h"#ifdef CONFIG_HAS_EARLYSUSPEND#include <linux/earlysuspend.h>#define DRV2665_SUS_LEVEL1#endif#ifdef CONFIG_PIEZO_ECO#define DEVICE_BUS 5#else#define DEVICE_BUS 19#endif#if 0/*** I2C FW info*/typedef struct _FWINFO{ char cMajorVersion; char cMinorVersion; char cInterfaceType; /* 3 = I2C */ char cAmpType[9]; /* 1 = differential amp */ char cAmpUsage; /* 1 = single boost, 2 = dual boost */ char cClockSource; /* 2 = external 18MHz clock */} FWINFO;FWINFO g_FWInfo;#define VIBE_MAX_DEVICE_NAME_LENGTH64static char g_szFWVersion[VIBE_MAX_DEVICE_NAME_LENGTH];#endif#define DRV2665_DRV_NAME"ti_drv2665"#define DRV2665_I2C_ADDRESS0x59#define DRV2665_CHIP_ID0x05#define DRV2665_AUTOSUSPEND_DELAY5000#define DRV2665_FIFO_DEPTH100#define DRV2665_MSG_BUFFER_SIZEPAGE_SIZE#define DRV2665_STATUS_REG0x00#defineDRV2665_STATUS_MASK0x03#define DRV2665_CONTROL_REG0x01#defineDRV2665_ID_MASK0x78#defineDRV2665_ID_SHIFT0x03#defineDRV2665_CONTROL_WRITE_MASK0x07#define DRV2665_CONTROL2_REG0x02#defineDRV2665_DEV_RST_SHIFT0x07#defineDRV2665_DEV_RST(1 << DRV2665_DEV_RST_SHIFT)#defineDRV2665_STANDBY_SHIFT0x06#defineDRV2665_STANDBY(1 << DRV2665_STANDBY_SHIFT)#defineDRV2665_TIMEOUT_SHIFT0x02#defineDRV2665_05MS_TIMEOUT0x00#defineDRV2665_10MS_TIMEOUT0x01#defineDRV2665_15MS_TIMEOUT0x02#defineDRV2665_20MS_TIMEOUT0x03#define DRV2665_FIFO_REG0x0B/*Following code is for setting gain for DRV2665 in register 1 bit 0 and bit 10x00 :output is set for +/-25V :For AAc, this is more useful for +/-30V actuator0x01 :output is set for +/-50V0x02 :output is set for +/-75V :we use this for SEMCO0x03 :output is set for +/-100V*/#define DRV2665_GAIN_50 0x00#define DRV2665_GAIN_100 0x01#define DRV2665_GAIN_150 0x02#define DRV2665_GAIN_200 0x03struct drv2665_data {//--------Timer ---------//struct i2c_client *client;struct timed_output_dev timed_dev;struct hrtimer timer;struct work_struct work;u32 max_runtime_ms;u32 time_chunk_ms;u32 runtime_left;#ifdef CONFIG_HAS_EARLYSUSPENDstruct early_suspend es;#endif//----------------------//struct mutex lock;/* DRV2665 cached registers */u8 control;u8 control2;/* DRV2665_FIFO_DEPTH + additional 1 byte for fifo cmd register */u8 fifo_buff[DRV2665_FIFO_DEPTH + 1];};static int drv2665_read_byte(struct device *dev, u8 reg, const char *reg_name){int val;val = i2c_smbus_read_byte_data(to_i2c_client(dev), reg);if (val < 0)dev_err(dev, "error reading %s register\n", reg_name);return val;}static int drv2665_write_byte(struct device *dev, u8 reg, u8 data,const char *reg_name){int status;status = i2c_smbus_write_byte_data(to_i2c_client(dev), reg, data);if (status < 0)dev_err(dev, "error writing %s register\n", reg_name);return status;}static ssize_t status_show(struct device *dev, struct device_attribute *attr,char *buf){int val;val = drv2665_read_byte(dev, DRV2665_STATUS_REG, "status");if (val < 0)return val;return sprintf(buf, "%x\n", val & DRV2665_STATUS_MASK);}static DEVICE_ATTR(status, S_IRUGO, status_show, NULL);static ssize_t fifo_store(struct device *dev, struct device_attribute *attr,const char *buf, size_t count){int status;struct i2c_client *client;struct drv2665_data *data;struct i2c_msg msg;/* * make sure that count is between 1 and DRV2665_FIFO_DEPTH both * inclusive */if (count < 1)return -EINVAL;if (count > DRV2665_FIFO_DEPTH)return -ENOSPC;memset(&msg, 0x00, sizeof(msg));client = to_i2c_client(dev);data = i2c_get_clientdata(client);mutex_lock(&data->lock);/* DRV2665 FIFO Register Address */data->fifo_buff[0] = DRV2665_FIFO_REG;/* fill remaining fifo_buff with data from user space */memcpy(data->fifo_buff + 1, buf, count);msg.addr = client->addr;msg.flags = 0;msg.buf = data->fifo_buff;msg.len = count + 1;//msg.transferred = 0;status = i2c_transfer(client->adapter, &msg, 1);mutex_unlock(&data->lock);/* * subtract DRV2665_FIFO_REG byte from successfully * transferred bytes *///return msg.transferred - 1;return count;}static ssize_t fifo_show(struct device *dev,struct device_attribute *attr, char *buf){struct i2c_client *client;struct drv2665_data *data;int i, count = 0, count_total = 0;char * buf_tmp;client = to_i2c_client(dev);data = i2c_get_clientdata(client);mutex_lock(&data->lock);buf_tmp = buf;for(i=0;i<DRV2665_FIFO_DEPTH;i++){count = sprintf(buf_tmp, "%d\n", data->fifo_buff[i]);buf_tmp += count;count_total += count;}mutex_unlock(&data->lock);return count_total;}static DEVICE_ATTR(fifo, S_IRUGO | S_IWUSR, fifo_show, fifo_store);static ssize_t control_show(struct device *dev,struct device_attribute *attr, char *buf){int val;val = drv2665_read_byte(dev, DRV2665_CONTROL_REG, "control");if (val < 0)return val;return sprintf(buf, "%x\n", val);}static ssize_t control_store(struct device *dev,struct device_attribute *attr, const char *buf, size_t count){u8 val;int status;struct drv2665_data *data;ssize_t ret_value;data = dev_get_drvdata(dev);status = kstrtou8(buf, 16, &val);if (status)return status;mutex_lock(&data->lock);status = drv2665_write_byte(dev, DRV2665_CONTROL_REG,val & DRV2665_CONTROL_WRITE_MASK, "control");if (status < 0) {ret_value = status;} else {/* cache DRV2665_CONTROL_REG value */data->control = val & DRV2665_CONTROL_WRITE_MASK;ret_value = 1;}mutex_unlock(&data->lock);return ret_value;}static DEVICE_ATTR(control, S_IRUGO | S_IWUSR, control_show, control_store);static ssize_t control2_show(struct device *dev,struct device_attribute *attr, char *buf){int val;val = drv2665_read_byte(dev, DRV2665_CONTROL2_REG, "control2");if (val < 0)return val;return sprintf(buf, "%x\n", val);}static ssize_t control2_store(struct device *dev,struct device_attribute *attr, const char *buf, size_t count){u8 val;int status;struct drv2665_data *data;ssize_t ret_value;data = dev_get_drvdata(dev);status = kstrtou8(buf, 16, &val);if (status)return status;mutex_lock(&data->lock);status = drv2665_write_byte(dev, DRV2665_CONTROL2_REG, val, "control2");if (status < 0) {ret_value = status;} else {/* cache DRV2665_CONTROL2_REG value */data->control2 = val;ret_value = 1;}mutex_unlock(&data->lock);return ret_value;}static DEVICE_ATTR(control2, S_IRUGO | S_IWUSR, control2_show, control2_store);static ssize_t reset_store(struct device *dev,struct device_attribute *attr, const char *buf, size_t count){u8 val;int status;struct drv2665_data *data;data = dev_get_drvdata(dev);status = kstrtou8(buf, 16, &val);if (status)return status;if (val != 1)return -EINVAL;mutex_lock(&data->lock);status = drv2665_write_byte(dev, DRV2665_CONTROL2_REG,DRV2665_DEV_RST, "control2");mutex_unlock(&data->lock);if (status < 0)return status;return 1;}static DEVICE_ATTR(reset, S_IWUSR, NULL, reset_store);static struct attribute *drv2665_attributes[] = {&dev_attr_reset.attr,&dev_attr_control2.attr,&dev_attr_control.attr,&dev_attr_status.attr,&dev_attr_fifo.attr,NULL};static const struct attribute_group drv2665_attr_group = {.attrs = drv2665_attributes,};//-------------------------------------------Timer ------------------------------------------//#define DRV2665_BYTES_PER_MS8 //8K HZ#define DRV2665_VIB_DEFAULT_VAL0x7F#define DRV2665_VIB_MAX_TIME5000#define DRV2665_FIFO_CHUNK_MS10#define DRV2665_GAIN DRV2665_GAIN_150#define DRV2665_TIMEOUT (DRV2665_20MS_TIMEOUT << DRV2665_TIMEOUT_SHIFT)u8 sin_buf[]={0,7,15,23,30,38,45,53,60,68,75,82,90,97,104,111,118,124,131,138,144,150,156,162,168,174,180,185,190,195,200,205,209,214,218,222,225,229,232,235,238,241,243,245,247,249,251,252,253,254,255,255,255,255,255,255,254,253,252,250,249,247,245,243,240,237,234,231,228,224,220,217,212,208,203,199,194,189,183,178,172,167,161,155,149,142,136,129,123,116,109,102,95,88,80,73,66,58,51,43,36,28,20,13,5,};static int drv2665_read_reg(struct i2c_client *client, u32 reg){int rc;rc = i2c_smbus_read_byte_data(client, reg);if (rc < 0)dev_err(&client->dev, "i2c reg read for 0x%x failed\n", reg);return rc;}static int drv2665_write_reg(struct i2c_client *client, u32 reg, u8 val){int rc;rc = i2c_smbus_write_byte_data(client, reg, val);if (rc < 0)dev_err(&client->dev, "i2c reg write for 0x%xfailed\n", reg);return rc;}static void drv2665_dump_regs(struct drv2665_data *data, char *label){dev_dbg(&data->client->dev,"%s: reg0x00 = 0x%x, reg0x01 = 0x%x reg0x02 = 0x%x", label,drv2665_read_reg(data->client, DRV2665_STATUS_REG),drv2665_read_reg(data->client, DRV2665_CONTROL_REG),drv2665_read_reg(data->client, DRV2665_CONTROL2_REG));}static void drv2665_worker(struct work_struct *work){struct drv2665_data *data;int rc = 0;u8 val;data = container_of(work, struct drv2665_data, work);/* data is played at 8khz */if (data->runtime_left < data->time_chunk_ms)val = data->runtime_left * DRV2665_BYTES_PER_MS;elseval = data->time_chunk_ms * DRV2665_BYTES_PER_MS;rc = i2c_master_send(data->client, data->fifo_buff, val + 1);if (rc < 0)dev_err(&data->client->dev, "i2c send message failed\n");}static void drv2665_enable(struct timed_output_dev *dev, int runtime){struct drv2665_data *data = container_of(dev, struct drv2665_data, timed_dev);unsigned long time_ms;if (runtime > data->max_runtime_ms) {dev_dbg(&data->client->dev, "Invalid runtime\n");runtime = data->max_runtime_ms;}mutex_lock(&data->lock);hrtimer_cancel(&data->timer);data->runtime_left = runtime;if (data->runtime_left < data->time_chunk_ms)time_ms = runtime * NSEC_PER_MSEC;elsetime_ms = data->time_chunk_ms * NSEC_PER_MSEC;hrtimer_start(&data->timer, ktime_set(0, time_ms), HRTIMER_MODE_REL);schedule_work(&data->work);mutex_unlock(&data->lock);}static int drv2665_get_time(struct timed_output_dev *dev){struct drv2665_data *data = container_of(dev, struct drv2665_data, timed_dev);if (hrtimer_active(&data->timer))returndata->runtime_left +ktime_to_ms(hrtimer_get_remaining(&data->timer));return 0;}static enum hrtimer_restart drv2665_timer(struct hrtimer *timer){struct drv2665_data *data;int time_ms;data = container_of(timer, struct drv2665_data, timer);if (data->runtime_left <= data->time_chunk_ms) {data->runtime_left = 0;schedule_work(&data->work);return HRTIMER_NORESTART;}data->runtime_left -= data->time_chunk_ms;if (data->runtime_left < data->time_chunk_ms)time_ms = data->runtime_left * NSEC_PER_MSEC;elsetime_ms = data->time_chunk_ms * NSEC_PER_MSEC;hrtimer_forward_now(&data->timer, ktime_set(0, time_ms));schedule_work(&data->work);return HRTIMER_RESTART;}static int drv2665_pm(bool enable){ int nRet; struct regulator *vreg_lvs4_1p8; vreg_lvs4_1p8 = regulator_get(NULL, "8921_lvs4"); if(IS_ERR(vreg_lvs4_1p8)) { nRet = PTR_ERR(vreg_lvs4_1p8); return -EIO; } if(enable) nRet = regulator_enable(vreg_lvs4_1p8); else nRet = regulator_disable(vreg_lvs4_1p8); msleep(50); if(nRet<0) { return -EIO; } regulator_put(vreg_lvs4_1p8); return nRet;}#ifdef CONFIG_PMstatic int drv2665_suspend(struct device *dev){struct drv2665_data *data = dev_get_drvdata(dev);u8 val;int rc;hrtimer_cancel(&data->timer);cancel_work_sync(&data->work);/* set standby */val = data->control2 | DRV2665_STANDBY;rc = drv2665_write_reg(data->client, DRV2665_CONTROL2_REG, val);if (rc < 0)dev_err(dev, "unable to set standby\n");/* turn regulators off */drv2665_pm(false);return 0;}static int drv2665_resume(struct device *dev){struct drv2665_data *data = dev_get_drvdata(dev);int rc;/* turn regulators on */drv2665_pm(true);if (rc < 0) {dev_err(dev, "unable to turn regulators on\n");return rc;}/* clear standby */rc = drv2665_write_reg(data->client,DRV2665_CONTROL2_REG, data->control2);if (rc < 0) {dev_err(dev, "unable to clear standby\n");goto vreg_off;}return 0;vreg_off:drv2665_pm(false);return rc;}#ifdef CONFIG_HAS_EARLYSUSPENDstatic void drv2665_early_suspend(struct early_suspend *es){struct drv2665_data *data = container_of(es, struct drv2665_data, es);drv2665_suspend(&data->client->dev);}static void drv2665_late_resume(struct early_suspend *es){struct drv2665_data *data = container_of(es, struct drv2665_data, es);drv2665_resume(&data->client->dev);}#endifstatic const struct dev_pm_ops drv2665_pm_ops = {#ifndef CONFIG_HAS_EARLYSUSPEND.suspend = drv2665_suspend,.resume = drv2665_resume,#endif};#endif//-----------------------------------------Timer end-----------------------------------------//static int __devinit drv2665_probe(struct i2c_client *client,const struct i2c_device_id *id){int status;struct drv2665_data *data;int rc;//char buf[1] = {0};drv2665_pm(true);data = kzalloc(sizeof(*data), GFP_KERNEL);if (!data) {status = -ENOMEM;dev_err(&client->dev, "%s: kzalloc failed\n", __func__);goto exit;}/* register sysfs hooks */status = sysfs_create_group(&client->dev.kobj, &drv2665_attr_group);if (status) {dev_err(&client->dev, "sysfs_create_group failed\n");goto exit_free;}/* * turn on the chip with max timeout of 20ms * save this configuration in dev->control2 */data->control2 = DRV2665_20MS_TIMEOUT << DRV2665_TIMEOUT_SHIFT;status = drv2665_write_byte(&client->dev, DRV2665_CONTROL2_REG,data->control2, "control2");if (status < 0)goto exit_sysfs;status = drv2665_write_byte(&client->dev, DRV2665_CONTROL_REG,DRV2665_GAIN, "control");if (status < 0)goto exit_sysfs;mutex_init(&data->lock);i2c_set_clientdata(client, data);#if 0if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) { dev_err(&client->dev, "i2c_check_functionality failed.\n"); return -ENODEV; } else { dev_info(&client->dev, "addr=0x%02x name=%s\n",client->addr, client->name); /* Retrieve the FW version for information purposes */ dev_info(&client->dev, "send to master, buf=%s\n",buf); i2c_master_send(client, (char*)(&buf), 1); dev_info(&client->dev, "recv from master, buf=%s\n",buf); if ((!client) || (i2c_master_recv(client, (char*)(&g_FWInfo), sizeof(FWINFO)) != sizeof(FWINFO))) { /* Could not retrieve the FW info. Writing ? as an indication */ dev_err(&client->dev, "Could not retrieve the FW info. Writing ? as an indication\n"); sprintf(g_szFWVersion, "[FW: ?]"); } else { sprintf(g_szFWVersion, "[FW: v%d.%d]", g_FWInfo.cMajorVersion, g_FWInfo.cMinorVersion); } } dev_info(&client->dev, "drv2605_probe end. %s\n",g_szFWVersion);#endif//------------------Timer ------------//data->client = client;data->max_runtime_ms = DRV2665_VIB_MAX_TIME;INIT_WORK(&data->work, drv2665_worker);hrtimer_init(&data->timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL);data->timer.function = drv2665_timer;data->fifo_buff[0] = DRV2665_FIFO_REG;memcpy(data->fifo_buff+1, sin_buf, DRV2665_FIFO_DEPTH-1);data->time_chunk_ms = DRV2665_FIFO_CHUNK_MS;drv2665_dump_regs(data, "new");/* register with timed output class */data->timed_dev.name = "vibrator";data->timed_dev.get_time = drv2665_get_time;data->timed_dev.enable = drv2665_enable;rc = timed_output_dev_register(&data->timed_dev);if (rc) {dev_err(&client->dev, "unable to register with timed_output\n");goto exit_sysfs;}#ifdef CONFIG_HAS_EARLYSUSPENDdata->es.level = EARLY_SUSPEND_LEVEL_BLANK_SCREEN + DRV2665_SUS_LEVEL;data->es.suspend = drv2665_early_suspend;data->es.resume = drv2665_late_resume;register_early_suspend(&data->es);#endifdev_info(&client->dev, "initialized successfully\n");return 0;exit_sysfs:sysfs_remove_group(&client->dev.kobj, &drv2665_attr_group);exit_free:kfree(data);exit:return status;}static int __devexit drv2665_remove(struct i2c_client *client){struct drv2665_data *data;data = i2c_get_clientdata(client);/* unregister sysfs hooks */sysfs_remove_group(&client->dev.kobj, &drv2665_attr_group);#ifdef CONFIG_HAS_EARLYSUSPENDunregister_early_suspend(&data->es);#endifmutex_destroy(&data->lock);timed_output_dev_unregister(&data->timed_dev);hrtimer_cancel(&data->timer);cancel_work_sync(&data->work);kfree(data);return 0;}#if 0static int drv2665_detect(struct i2c_client *client,struct i2c_board_info *info){int val;if (!i2c_check_functionality(client->adapter,I2C_FUNC_SMBUS_BYTE_DATA))return -ENODEV;val = drv2665_read_byte(&client->dev, DRV2665_CONTROL_REG, "control");if (val < 0){dev_info(&client->dev, "control error\n");return -ENODEV;}val = (val & DRV2665_ID_MASK) >> DRV2665_ID_SHIFT;if (val != DRV2665_CHIP_ID){dev_info(&client->dev, "DRV2665_CHIP_ID error\n");return -ENODEV;}elsestrlcpy(info->type, DRV2665_DRV_NAME, I2C_NAME_SIZE);return 0;}#endifstatic const struct i2c_device_id drv2665_id[] = {{ DRV2665_DRV_NAME, 0 },{ }};MODULE_DEVICE_TABLE(i2c, drv2665_id);static const unsigned short normal_i2c[] = { DRV2665_I2C_ADDRESS,I2C_CLIENT_END };static struct i2c_driver drv2665_driver = {.driver = {.name= DRV2665_DRV_NAME,.owner = THIS_MODULE,#ifdef CONFIG_PM.pm = &drv2665_pm_ops,#endif},.id_table= drv2665_id,.probe= drv2665_probe,//.detect= drv2665_detect,.address_list= normal_i2c,.remove= __devexit_p(drv2665_remove),};static struct i2c_board_info info = { I2C_BOARD_INFO(DRV2665_DRV_NAME, DRV2665_I2C_ADDRESS),};static int __init drv2665_init(void){ struct i2c_adapter* adapter; /* Initialized below. */ struct i2c_client* client; /* Initialized below. */ adapter = i2c_get_adapter(DEVICE_BUS); if (adapter) { pr_info("found device bus %d\n",DEVICE_BUS); client = i2c_new_device(adapter, &info); if (client) { int retVal = i2c_add_driver(&drv2665_driver); if (retVal) { pr_err("drv2665_driver: Cannot add driver.\n"); return -ENODEV; } pr_err("added driver. retVal=%d addr=0x%02x name=%s\n",retVal, client->addr, client->name); return retVal; } else { pr_err("drv2665_driver: Cannot create new device.\n"); return -ENODEV; } } else { pr_err("drv2665_driver: I2C Adapter not found.\n"); return -ENODEV; } return -ENODEV;}static void __exit drv2665_exit(void){i2c_del_driver(&drv2665_driver);}module_init(drv2665_init);module_exit(drv2665_exit);MODULE_AUTHOR("syhost <soyudesign@gmail.com>");MODULE_DESCRIPTION("DRV2665 Piezo Haptic Driver");MODULE_LICENSE("GPL v2");
将此代码保存成ti_drv2665.c 放到drivers/misc下, 同时修改该目录下的Makefile:
#obj-$(CONFIG_PIEZO) += tspdrv/obj-$(CONFIG_PIEZO) += ti_drv2665.o
同时还需要将标准的震动驱动的config禁止掉, 在config文件里:
CONFIG_PMIC8XXX_VIBRATOR=n
此震动驱动跟泛泰A860官方的完全不一样, 可以兼容Andorid标准的震动控制路径:
/sys/class/timed_output/vibrator/enable
默认的PIEZO的震动驱动模型为(sin函数):
0 0
- 泛泰A860 PIZEO /TS5000协议/TI drv2665芯片 Android内核震动驱动源码
- 泛泰A860 官方振动源码(TI drv2665驱动芯片)(非之前的tspdrv代码)
- CC2530芯片在TI协议栈中自定义外部中断
- TI am335x 内核源码编译命令等
- Linux3.2 源码驱动分布(TI)
- ANT+协议ARM内核芯片NRF51422介绍
- 泛泰A860版本的识别
- Android 铃声多媒体音量、静音、震动!震动!震动!哈哈(附源码)
- 芯片DAC7731 (TI)
- TI芯片TMS320DM642开发
- Linux内核驱动之网络驱动(4)PHY芯片分析
- Android 模拟器(源码编译)测试内核驱动
- [Android 4.4.3] 泛泰A860 Omni4.4.3 20140610 RC2.0 三版通刷 by syhost
- [Android 4.4.3] 泛泰A860 Omni4.4.3 20140610 RC2.0 三版通刷 by syhost
- [Android 4.4.3] 泛泰A860 Omni4.4.3 20140610 RC2.0 三版通刷 by syhost
- TI android 源码下载镜像
- TI-beaglebone上扩展的DS1302时钟芯片的驱动模块
- lwip协议的深入理解(结合目前主流的stm32,ti,esp8266等芯片讲解)
- 关于ArcGIS Engine中获取权限的问题
- 网站优化SEO如何稳定
- SEO优化之提高优化的效率的四个技巧
- mysql注入初体验
- jquery validate日期与身份证验证实例
- 泛泰A860 PIZEO /TS5000协议/TI drv2665芯片 Android内核震动驱动源码
- 安装 黑苹果
- 地方网站站长的知识结构
- 地方网站站长的知识结构
- 总结一下seo
- opencv中的图像亮度变换(图像增强)
- USACO ride
- UVa 673 括号平衡
- 1Z0-051 QUESTION 25 表名应该注意的地方和CLOB应该注意的地方