| /* |
| * drivers/input/touchscreen/scn0700.c |
| * |
| * Copyright (C) 2004-2012, Ambarella, Inc. |
| * Long Zhao <longzhao@ambarella.com> |
| * |
| * |
| * This program is free software; you can redistribute it and/or modify |
| * it under the terms of the GNU General Public License version 2 as |
| * published by the Free Software Foundation. |
| */ |
| |
| #include <linux/module.h> |
| #include <linux/slab.h> |
| #include <linux/delay.h> |
| #include <linux/input.h> |
| #include <linux/interrupt.h> |
| #include <linux/i2c.h> |
| #include <linux/i2c/scn0700.h> |
| |
| #ifdef CONFIG_DEBUG_TOUCHSCREEN |
| #define SCN0700_DEBUG(format, arg...) printk(format , ## arg) |
| #else |
| #define SCN0700_DEBUG(format, arg...) |
| #endif |
| |
| static int int_mode = 0x0A; |
| module_param(int_mode, int, 0644); |
| MODULE_PARM_DESC(int_mode, "Set INT mode."); |
| |
| #define MAX_Z 16 |
| #define MAX_FINGERS 2 |
| |
| typedef enum { |
| MSI_TOUCHING = 0x00, |
| MSI_OLD_TOUCHING, |
| MSI_X1_LOW, |
| MSI_X1_HIGH, |
| MSI_Y1_LOW, |
| MSI_Y1_HIGH, |
| MSI_X2_LOW, |
| MSI_X2_HIGH, |
| MSI_Y2_LOW, |
| MSI_Y2_HIGH, |
| |
| MSI_POWER_MODE = 0x14, |
| MSI_INT_MODE = 0x15, |
| } scn0700_sub_addr_t; |
| |
| #define MODE_ACTIVE (0x00) |
| #define MODE_SLEEP (0x01) |
| #define MODE_DEEP_SLEEP (0x02) |
| #define MODE_FREEZE (0x03) |
| #define MODE_AUTO_SWITCH (0xA4) |
| |
| #define INT_ASSERT_PERIOD (0x08) |
| #define INT_ASSERT_MOVING (0x09) |
| #define INT_ASSERT_TOUCH (0x0A) |
| |
| #define NUM_DATA 10 |
| |
| struct scn0700 { |
| char phys[32]; |
| struct input_dev *input; |
| struct i2c_client *client; |
| struct workqueue_struct *workqueue; |
| struct work_struct report_worker; |
| u8 reg_data[NUM_DATA]; |
| int irq; |
| struct scn0700_fix_data fix; |
| int (*get_pendown_state)(void); |
| void (*clear_penirq)(void); |
| }; |
| |
| static int scn0700_set_power(struct scn0700 *msi, u8 mode) |
| { |
| if (i2c_smbus_write_i2c_block_data(msi->client, |
| MSI_POWER_MODE, 1, &mode)) { |
| printk("I2C Error: %s\n", __func__); |
| return -EIO; |
| } else { |
| return 0; |
| } |
| } |
| |
| static int scn0700_config_irq(struct scn0700 *msi, u8 mode) |
| { |
| if (i2c_smbus_write_i2c_block_data(msi->client, |
| MSI_INT_MODE, 1, &mode)) { |
| printk("I2C Error: %s\n", __func__); |
| return -EIO; |
| } else { |
| return 0; |
| } |
| } |
| |
| static inline int scn0700_read_all(struct scn0700 *msi) |
| { |
| if (i2c_smbus_read_i2c_block_data(msi->client, |
| MSI_TOUCHING, NUM_DATA, msi->reg_data) != NUM_DATA) { |
| printk("I2C Error: %s\n", __func__); |
| return -EIO; |
| } else { |
| return 0; |
| } |
| } |
| |
| static void scn0700_send_event(struct scn0700 *msi) |
| { |
| struct input_dev *input = msi->input; |
| static int prev_touch = 0; |
| u8 i, t_num; |
| int x, y; |
| int event = 0; |
| |
| t_num = msi->reg_data[MSI_TOUCHING]; |
| |
| /* Button Pressed */ |
| if (!prev_touch && t_num) { |
| input_report_key(input, BTN_TOUCH, t_num); |
| SCN0700_DEBUG("Finger Pressed\n"); |
| scn0700_config_irq(msi, INT_ASSERT_PERIOD); |
| SCN0700_DEBUG("Switch int mode to period\n"); |
| } |
| |
| SCN0700_DEBUG("prev_touch=%d\n", prev_touch); |
| |
| /* Button Released */ |
| if (prev_touch && !t_num) { |
| event = 1; |
| input_report_abs(input, ABS_PRESSURE, 0); |
| input_report_key(input, BTN_TOUCH, 0); |
| input_report_abs(input, ABS_MT_TOUCH_MAJOR, 0); |
| input_mt_sync(input); |
| SCN0700_DEBUG("Finger Released\n\n\n"); |
| scn0700_config_irq(msi, INT_ASSERT_TOUCH); |
| SCN0700_DEBUG("Switch int mode to one-touch\n"); |
| } |
| |
| for(i=0; i<t_num; i++) { |
| x = (msi->reg_data[MSI_X1_HIGH + 4*i]<<8) | msi->reg_data[MSI_X1_LOW + 4*i]; |
| y = (msi->reg_data[MSI_Y1_HIGH + 4*i]<<8) | msi->reg_data[MSI_Y1_LOW + 4*i]; |
| SCN0700_DEBUG("Finger%d Raw: (%d, %d)\n", i+1, x, y); |
| |
| if (x < msi->fix.x_min) { |
| x = msi->fix.x_min; |
| } |
| if (x > msi->fix.x_max) { |
| x = msi->fix.x_max; |
| } |
| if (y < msi->fix.y_min) { |
| y = msi->fix.y_min; |
| } |
| if (y > msi->fix.y_max) { |
| y = msi->fix.y_max; |
| } |
| if (msi->fix.x_invert) { |
| x = msi->fix.x_max - x + msi->fix.x_min; |
| } |
| if (msi->fix.y_invert) { |
| y = msi->fix.y_max - y + msi->fix.y_min; |
| } |
| SCN0700_DEBUG("Finger%d Calibrated: (%d, %d)\n", i+1, x, y); |
| event = 1; |
| if (t_num == 1) { |
| input_report_abs(input, ABS_PRESSURE, MAX_Z); |
| input_report_abs(input, ABS_X, x); |
| input_report_abs(input, ABS_Y, y); |
| SCN0700_DEBUG("Report single point\n"); |
| } |
| input_report_abs(input, ABS_MT_TOUCH_MAJOR, MAX_Z); |
| input_report_abs(input, ABS_MT_POSITION_X, x); |
| input_report_abs(input, ABS_MT_POSITION_Y, y); |
| input_mt_sync(input); |
| SCN0700_DEBUG("Report multi point\n"); |
| } |
| |
| if (event) { |
| input_sync(input); |
| } |
| prev_touch = t_num; |
| } |
| |
| static irqreturn_t scn0700_irq(int irq, void *handle) |
| { |
| struct scn0700 *msi = handle; |
| |
| if (msi->get_pendown_state && !msi->get_pendown_state()) |
| goto scn0700_irq_exit; |
| |
| if (msi->clear_penirq) { |
| msi->clear_penirq(); |
| } |
| |
| queue_work(msi->workqueue, &msi->report_worker); |
| |
| scn0700_irq_exit: |
| return IRQ_HANDLED; |
| } |
| |
| static void scn0700_report_worker(struct work_struct *work) |
| { |
| struct scn0700 *msi; |
| |
| #ifdef CONFIG_DEBUG_TOUCHSCREEN |
| int T_num, T_num_old; |
| int X1, Y1, X2, Y2; |
| #endif |
| |
| msi = container_of(work, struct scn0700, report_worker); |
| scn0700_read_all(msi); |
| |
| #ifdef CONFIG_DEBUG_TOUCHSCREEN |
| X1 = (msi->reg_data[MSI_X1_HIGH]<<8) | msi->reg_data[MSI_X1_LOW]; |
| Y1 = (msi->reg_data[MSI_Y1_HIGH]<<8) | msi->reg_data[MSI_Y1_LOW]; |
| X2 = (msi->reg_data[MSI_X2_HIGH]<<8) | msi->reg_data[MSI_X2_LOW]; |
| Y2 = (msi->reg_data[MSI_Y2_HIGH]<<8) | msi->reg_data[MSI_Y2_LOW]; |
| T_num = msi->reg_data[MSI_TOUCHING]; |
| T_num_old = msi->reg_data[MSI_OLD_TOUCHING]; |
| |
| SCN0700_DEBUG("(X1, Y1)=(%d, %d)\n", X1, Y1); |
| SCN0700_DEBUG("(X2, Y2)=(%d, %d)\n", X2, Y2); |
| SCN0700_DEBUG("T_num=%d\n", T_num); |
| SCN0700_DEBUG("T_num_old=%d\n", T_num_old); |
| #endif |
| |
| scn0700_send_event(msi); |
| } |
| |
| static int scn0700_probe(struct i2c_client *client, |
| const struct i2c_device_id *id) |
| { |
| struct input_dev *input_dev; |
| struct scn0700 *msi; |
| struct scn0700_platform_data *pdata; |
| int err; |
| |
| pdata = client->dev.platform_data; |
| if (!pdata) { |
| dev_err(&client->dev, "platform data is required!\n"); |
| return -EINVAL; |
| } |
| pdata->init_platform_hw(); |
| |
| if (!i2c_check_functionality(client->adapter, |
| I2C_FUNC_SMBUS_WRITE_BYTE_DATA | I2C_FUNC_SMBUS_WRITE_BYTE | |
| I2C_FUNC_SMBUS_READ_BYTE)) |
| return -EIO; |
| |
| msi = kzalloc(sizeof(struct scn0700), GFP_KERNEL); |
| input_dev = input_allocate_device(); |
| if (!msi || !input_dev) { |
| err = -ENOMEM; |
| goto err_free_mem; |
| } |
| |
| msi->client = client; |
| i2c_set_clientdata(client, msi); |
| msi->input = input_dev; |
| msi->get_pendown_state = pdata->get_pendown_state; |
| msi->clear_penirq = pdata->clear_penirq; |
| snprintf(msi->phys, sizeof(msi->phys), |
| "%s/input0", dev_name(&client->dev)); |
| |
| err = scn0700_set_power(msi, MODE_ACTIVE); |
| if (err) |
| goto err_free_mem; |
| |
| msi->fix = pdata->fix; |
| |
| err = scn0700_config_irq(msi, int_mode); |
| if (err) |
| goto err_free_mem; |
| |
| err = scn0700_read_all(msi); |
| if (err) |
| goto err_free_mem; |
| |
| input_dev->name = "Data Image SCN0700 Touchscreen"; |
| input_dev->phys = msi->phys; |
| input_dev->id.bustype = BUS_I2C; |
| set_bit(EV_SYN, input_dev->evbit); |
| set_bit(EV_KEY, input_dev->evbit); |
| set_bit(BTN_TOUCH, input_dev->keybit); |
| set_bit(EV_ABS, input_dev->evbit); |
| |
| input_set_abs_params(input_dev, ABS_PRESSURE, 0, MAX_Z, 0, 0); |
| input_set_abs_params(input_dev, ABS_X, msi->fix.x_min, msi->fix.x_max, 0, 0); |
| input_set_abs_params(input_dev, ABS_Y, msi->fix.y_min, msi->fix.y_max, 0, 0); |
| |
| input_set_abs_params(input_dev, ABS_MT_TOUCH_MAJOR, 0, MAX_Z, 0, 0); |
| input_set_abs_params(input_dev, ABS_MT_POSITION_X, msi->fix.x_min, msi->fix.x_max, 0, 0); |
| input_set_abs_params(input_dev, ABS_MT_POSITION_Y, msi->fix.y_min, msi->fix.y_max, 0, 0); |
| |
| msi->workqueue = create_singlethread_workqueue("scn0700"); |
| INIT_WORK(&msi->report_worker, scn0700_report_worker); |
| |
| msi->irq = client->irq; |
| err = request_irq(msi->irq, scn0700_irq, IRQF_TRIGGER_FALLING, |
| client->dev.driver->name, msi); |
| if (err < 0) { |
| dev_err(&client->dev, "irq %d busy?\n", msi->irq); |
| goto err_free_mem; |
| } |
| |
| err = input_register_device(input_dev); |
| if (err) |
| goto err_free_irq; |
| |
| dev_info(&client->dev, "%s is probed!\n", input_dev->name); |
| |
| return 0; |
| |
| err_free_irq: |
| free_irq(msi->irq, msi); |
| err_free_mem: |
| input_free_device(input_dev); |
| kfree(msi); |
| return err; |
| } |
| |
| static int scn0700_remove(struct i2c_client *client) |
| { |
| struct scn0700 *msi = i2c_get_clientdata(client); |
| struct scn0700_platform_data *pdata = client->dev.platform_data; |
| |
| pdata->exit_platform_hw(); |
| destroy_workqueue(msi->workqueue); |
| free_irq(msi->irq, msi); |
| input_unregister_device(msi->input); |
| kfree(msi); |
| |
| return 0; |
| } |
| |
| static struct i2c_device_id scn0700_idtable[] = { |
| { "scn0700", 0 }, |
| { } |
| }; |
| |
| MODULE_DEVICE_TABLE(i2c, scn0700_idtable); |
| |
| static struct i2c_driver scn0700_driver = { |
| .driver = { |
| .owner = THIS_MODULE, |
| .name = "scn0700" |
| }, |
| .id_table = scn0700_idtable, |
| .probe = scn0700_probe, |
| .remove = scn0700_remove, |
| }; |
| |
| static int __init scn0700_init(void) |
| { |
| return i2c_add_driver(&scn0700_driver); |
| } |
| |
| static void __exit scn0700_exit(void) |
| { |
| i2c_del_driver(&scn0700_driver); |
| } |
| |
| module_init(scn0700_init); |
| module_exit(scn0700_exit); |
| |
| MODULE_AUTHOR("Long Zhao <longzhao@ambarella.com>"); |
| MODULE_DESCRIPTION("Data Image SCN0700 TouchScreen Driver"); |
| MODULE_LICENSE("GPL"); |