| /* |
| * drivers/input/touchscreen/hx8526.c |
| * |
| * Copyright (C) 2004-2012, Ambarella, Inc. |
| * Zhenwu Xue <zwxue@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/hx8526.h> |
| |
| #ifdef CONFIG_DEBUG_TOUCHSCREEN |
| #define HX8526_DEBUG(format, arg...) printk(format , ## arg) |
| #else |
| #define HX8526_DEBUG(format, arg...) |
| #endif |
| |
| #define MAX_Z 16 |
| #define MAX_FINGERS 4 |
| |
| typedef enum { |
| HX_X0_HI = 0x00, |
| HX_X0_LO, |
| HX_Y0_HI, |
| HX_Y0_LO, |
| HX_X1_HI, |
| HX_X1_LO, |
| HX_Y1_HI, |
| HX_Y1_LO, |
| HX_X2_HI, |
| HX_X2_LO, |
| HX_Y2_HI, |
| HX_Y2_LO, |
| HX_X3_HI, |
| HX_X3_LO, |
| HX_Y3_HI, |
| HX_Y3_LO, |
| |
| HX_SLEEP_OUT = 0x81, |
| HX_SENSE_ON = 0x83, |
| HX_READ_ALL_EVENT = 0x86, |
| } hx8526_sub_addr_t; |
| |
| #define NUM_DATA 24 |
| |
| struct hx8526 { |
| 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 hx8526_fix_data fix; |
| int (*get_pendown_state)(void); |
| void (*clear_penirq)(void); |
| }; |
| |
| static int hx8526_reg_init(struct hx8526 *hx) |
| { |
| u8 data[2]; |
| |
| if(i2c_smbus_write_i2c_block_data(hx->client, HX_SLEEP_OUT, 0, data)) { |
| printk("I2C Error: %s\n", __func__); |
| return -EIO; |
| } |
| |
| mdelay(1); |
| data[0] = 0x02; |
| if(i2c_smbus_write_i2c_block_data(hx->client, 0x42, 1, data)) { |
| printk("I2C Error: %s\n", __func__); |
| return -EIO; |
| } |
| |
| mdelay(200); |
| data[0] = 0x02; |
| if(i2c_smbus_write_i2c_block_data(hx->client, 0x35, 1, data)) { |
| printk("I2C Error: %s\n", __func__); |
| return -EIO; |
| } |
| |
| mdelay(1); |
| data[0] = 0x0f; |
| data[1] = 0x53; |
| if(i2c_smbus_write_i2c_block_data(hx->client, 0x36, 2, data)) { |
| printk("I2C Error: %s\n", __func__); |
| return -EIO; |
| } |
| |
| mdelay(1); |
| data[0] = 0x04; |
| data[1] = 0x02; |
| if(i2c_smbus_write_i2c_block_data(hx->client, 0xdd, 2, data)) { |
| printk("I2C Error: %s\n", __func__); |
| return -EIO; |
| } |
| |
| mdelay(1); |
| if(i2c_smbus_write_i2c_block_data(hx->client, HX_SENSE_ON, 0, data)) { |
| printk("I2C Error: %s\n", __func__); |
| return -EIO; |
| } |
| |
| return 0; |
| } |
| |
| static inline int hx8526_read_all(struct hx8526 *hx) |
| { |
| if (i2c_smbus_read_i2c_block_data(hx->client, |
| HX_READ_ALL_EVENT, NUM_DATA, hx->reg_data) != NUM_DATA) { |
| printk("I2C Error: %s\n", __func__); |
| return -EIO; |
| } else { |
| return 0; |
| } |
| } |
| |
| static void hx8526_send_event(struct hx8526 *hx) |
| { |
| struct input_dev *input = hx->input; |
| u8 i, finger; |
| static int prev_touch = 0; |
| static int curr_touch = 0; |
| int event = 0; |
| |
| switch (hx->reg_data[20]) { |
| case 0xf1: |
| case 0xf2: |
| case 0xf3: |
| case 0xf4: |
| curr_touch = hx->reg_data[20] & 0x0f; |
| break; |
| |
| default: |
| curr_touch = 0; |
| break; |
| } |
| |
| /* Button Pressed */ |
| if (!prev_touch && curr_touch) { |
| input_report_key(input, BTN_TOUCH, curr_touch); |
| HX8526_DEBUG("Finger Pressed\n"); |
| } |
| |
| /* Button Released */ |
| if (prev_touch && !curr_touch) { |
| 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); |
| HX8526_DEBUG("Finger Released\n\n\n"); |
| } |
| |
| for (i = 0, finger = 0; i < MAX_FINGERS; i++) { |
| u8 xh, xl, yh, yl; |
| u32 x, y; |
| |
| xh = hx->reg_data[HX_X0_HI + 4 * i]; |
| xl = hx->reg_data[HX_X0_LO + 4 * i]; |
| yh = hx->reg_data[HX_Y0_HI + 4 * i]; |
| yl = hx->reg_data[HX_Y0_LO + 4 * i]; |
| |
| if (xh == 0xff && xl == 0xff && yh == 0xff && yl == 0xff) { |
| continue; |
| } else { |
| finger++; |
| } |
| |
| xh = xh & 0x0f; |
| yh = yh & 0x0f; |
| x = (xh << 8) | xl; |
| y = (yh << 8) | yl; |
| HX8526_DEBUG("Finger%d Raw: (%d, %d)\n", finger, x, y); |
| |
| if (x < hx->fix.x_min) { |
| x = hx->fix.x_min; |
| } |
| if (x > hx->fix.x_max) { |
| x = hx->fix.x_max; |
| } |
| if (y < hx->fix.y_min) { |
| y = hx->fix.y_min; |
| } |
| if (y > hx->fix.y_max) { |
| y = hx->fix.y_max; |
| } |
| |
| if (hx->fix.x_invert) { |
| x = hx->fix.x_max - x + hx->fix.x_min; |
| } |
| |
| if (hx->fix.y_invert) { |
| y = hx->fix.y_max - y + hx->fix.y_min; |
| } |
| |
| event = 1; |
| if (finger == 1) { |
| input_report_abs(input, ABS_PRESSURE, MAX_Z); |
| input_report_abs(input, ABS_X, x); |
| input_report_abs(input, ABS_Y, y); |
| } |
| |
| 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); |
| HX8526_DEBUG("Finger%d Calibrated: (%d, %d)\n", finger, x, y); |
| } |
| |
| if (event) |
| input_sync(input); |
| prev_touch = curr_touch; |
| } |
| |
| static irqreturn_t hx8526_irq(int irq, void *handle) |
| { |
| struct hx8526 *hx = handle; |
| |
| if (hx->get_pendown_state && !hx->get_pendown_state()) |
| goto hx8526_irq_exit; |
| |
| if (hx->clear_penirq) { |
| hx->clear_penirq(); |
| } |
| |
| queue_work(hx->workqueue, &hx->report_worker); |
| |
| hx8526_irq_exit: |
| return IRQ_HANDLED; |
| } |
| |
| static void hx8526_report_worker(struct work_struct *work) |
| { |
| struct hx8526 *hx; |
| |
| hx = container_of(work, struct hx8526, report_worker); |
| |
| hx8526_read_all(hx); |
| hx8526_send_event(hx); |
| } |
| |
| static int hx8526_probe(struct i2c_client *client, |
| const struct i2c_device_id *id) |
| { |
| struct input_dev *input_dev; |
| struct hx8526 *hx; |
| struct hx8526_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; |
| |
| hx = kzalloc(sizeof(struct hx8526), GFP_KERNEL); |
| input_dev = input_allocate_device(); |
| if (!hx || !input_dev) { |
| err = -ENOMEM; |
| goto err_free_mem; |
| } |
| |
| hx->client = client; |
| i2c_set_clientdata(client, hx); |
| hx->input = input_dev; |
| hx->get_pendown_state = pdata->get_pendown_state; |
| hx->clear_penirq = pdata->clear_penirq; |
| snprintf(hx->phys, sizeof(hx->phys), |
| "%s/input0", dev_name(&client->dev)); |
| |
| err = hx8526_reg_init(hx); |
| if (err) |
| goto err_free_mem; |
| |
| hx->fix = pdata->fix[HX8526_FAMILY_0]; |
| |
| err = hx8526_read_all(hx); |
| if (err) |
| goto err_free_mem; |
| |
| input_dev->name = "Synaptics HX8526 Touchscreen"; |
| input_dev->phys = hx->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, hx->fix.x_min, hx->fix.x_max, 0, 0); |
| input_set_abs_params(input_dev, ABS_Y, hx->fix.y_min, hx->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, hx->fix.x_min, hx->fix.x_max, 0, 0); |
| input_set_abs_params(input_dev, ABS_MT_POSITION_Y, hx->fix.y_min, hx->fix.y_max, 0, 0); |
| |
| hx->workqueue = create_singlethread_workqueue("hx8526"); |
| INIT_WORK(&hx->report_worker, hx8526_report_worker); |
| |
| hx->irq = client->irq; |
| err = request_irq(hx->irq, hx8526_irq, IRQF_TRIGGER_FALLING, |
| client->dev.driver->name, hx); |
| if (err < 0) { |
| dev_err(&client->dev, "irq %d busy?\n", hx->irq); |
| goto err_free_mem; |
| } |
| |
| err = input_register_device(input_dev); |
| if (err) |
| goto err_free_irq; |
| |
| return 0; |
| |
| err_free_irq: |
| free_irq(hx->irq, hx); |
| err_free_mem: |
| input_free_device(input_dev); |
| kfree(hx); |
| return err; |
| } |
| |
| static int hx8526_remove(struct i2c_client *client) |
| { |
| struct hx8526 *hx = i2c_get_clientdata(client); |
| struct hx8526_platform_data *pdata = client->dev.platform_data; |
| |
| pdata->exit_platform_hw(); |
| destroy_workqueue(hx->workqueue); |
| free_irq(hx->irq, hx); |
| input_unregister_device(hx->input); |
| kfree(hx); |
| |
| return 0; |
| } |
| |
| static struct i2c_device_id hx8526_idtable[] = { |
| { "hx8526", 0 }, |
| { } |
| }; |
| |
| MODULE_DEVICE_TABLE(i2c, hx8526_idtable); |
| |
| static struct i2c_driver hx8526_driver = { |
| .driver = { |
| .owner = THIS_MODULE, |
| .name = "hx8526" |
| }, |
| .id_table = hx8526_idtable, |
| .probe = hx8526_probe, |
| .remove = hx8526_remove, |
| }; |
| |
| static int __init hx8526_init(void) |
| { |
| return i2c_add_driver(&hx8526_driver); |
| } |
| |
| static void __exit hx8526_exit(void) |
| { |
| i2c_del_driver(&hx8526_driver); |
| } |
| |
| module_init(hx8526_init); |
| module_exit(hx8526_exit); |
| |
| MODULE_AUTHOR("Zhenwu Xue <zwxue@ambarella.com>"); |
| MODULE_DESCRIPTION("HiMax HX8526 TouchScreen Driver"); |
| MODULE_LICENSE("GPL"); |