From 3a5c947ef9e93fbf4f2d0123dcf888ea067d09e3 Mon Sep 17 00:00:00 2001 From: Du, Dudley Date: Thu, 25 Aug 2011 16:44:10 +0800 Subject: [PATCH 3/4] input:cyapa: add firmware update interfaces for cyapa driver Add interfaces to support updating Cypress I2C touchpad device firmware image through userspace. BUG= TEST=test on tegra2 seaboard platform with cyapa_fw_update tool Test command to update firmware image as below: ./cyapa_fw_update CYTRA_002002_V3.iic -f Signed-off-by: Du, Dudley --- drivers/input/mouse/cypress_i2c.c | 504 ++++++++++++++++++++++++++++++++++++- 1 files changed, 503 insertions(+), 1 deletions(-) diff --git a/drivers/input/mouse/cypress_i2c.c b/drivers/input/mouse/cypress_i2c.c index 7240632..20f0c24 100644 --- a/drivers/input/mouse/cypress_i2c.c +++ b/drivers/input/mouse/cypress_i2c.c @@ -312,6 +312,8 @@ struct cyapa_i2c { int physical_size_y; }; +static unsigned char bl_switch_active[] = {0x00, 0xFF, 0x38, + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07}; static unsigned char bl_switch_idle[] = {0x00, 0xFF, 0x3B, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07}; static unsigned char bl_app_launch[] = {0x00, 0xFF, 0xA5, @@ -423,6 +425,42 @@ static void cyapa_disable_irq(struct cyapa_i2c *touch) spin_unlock_irqrestore(&touch->miscdev_spinlock, flags); } +static void cyapa_bl_enable_irq(struct cyapa_i2c *touch) +{ + unsigned long flags; + + spin_lock_irqsave(&touch->miscdev_spinlock, flags); + if (touch->polling_mode_enabled) + goto out; + + touch->bl_irq_enable = true; + if (!touch->irq_enabled) { + touch->irq_enabled = true; + enable_irq(touch->irq); + } + +out: + spin_unlock_irqrestore(&touch->miscdev_spinlock, flags); +} + +static void cyapa_bl_disable_irq(struct cyapa_i2c *touch) +{ + unsigned long flags; + + spin_lock_irqsave(&touch->miscdev_spinlock, flags); + if (touch->polling_mode_enabled) + goto out; + + touch->bl_irq_enable = false; + if (touch->irq_enabled) { + touch->irq_enabled = false; + disable_irq(touch->irq); + } + +out: + spin_unlock_irqrestore(&touch->miscdev_spinlock, flags); +} + static int cyapa_acquire_i2c_bus(struct cyapa_i2c *touch) { cyapa_disable_irq(touch); @@ -582,6 +620,184 @@ error: return (ret < 0) ? ret : (ret - 1); } + +/* + ************************************************************** + * misc cyapa device for trackpad firmware update, + * and for raw read/write operations. + * The following programs may open and use cyapa device. + * 1. X Input Driver. + * 2. trackpad firmware update program. + ************************************************************** + */ +static int cyapa_misc_open(struct inode *inode, struct file *file) +{ + int count; + unsigned long flags; + struct cyapa_i2c *touch = global_touch; + + if (touch == NULL) + return -ENODEV; + file->private_data = (void *)touch; + + spin_lock_irqsave(&touch->miscdev_spinlock, flags); + if (touch->misc_open_count) { + spin_unlock_irqrestore(&touch->miscdev_spinlock, flags); + return -EBUSY; + } + count = ++touch->misc_open_count; + spin_unlock_irqrestore(&touch->miscdev_spinlock, flags); + + return 0; +} + +static int cyapa_misc_close(struct inode *inode, struct file *file) +{ + int count; + unsigned long flags; + struct cyapa_i2c *touch = (struct cyapa_i2c *)file->private_data; + + spin_lock_irqsave(&touch->miscdev_spinlock, flags); + count = --touch->misc_open_count; + spin_unlock_irqrestore(&touch->miscdev_spinlock, flags); + + return 0; +} + +static int cyapa_pos_validate(unsigned int pos) +{ + return (pos >= 0) && (pos < CYAPA_REG_MAP_SIZE); +} + +static loff_t cyapa_misc_llseek(struct file *file, loff_t offset, int origin) +{ + loff_t ret = -EINVAL; + struct cyapa_i2c *touch = (struct cyapa_i2c *)file->private_data; + + if (touch == NULL) { + pr_err("cypress trackpad device does not exit.\n"); + return -ENODEV; + } + + mutex_lock(&touch->misc_mutex); + switch (origin) { + case SEEK_SET: + if (cyapa_pos_validate(offset)) { + file->f_pos = offset; + ret = file->f_pos; + } + break; + + case SEEK_CUR: + if (cyapa_pos_validate(file->f_pos + offset)) { + file->f_pos += offset; + ret = file->f_pos; + } + break; + + case SEEK_END: + if (cyapa_pos_validate(CYAPA_REG_MAP_SIZE + offset)) { + file->f_pos = (CYAPA_REG_MAP_SIZE + offset); + ret = file->f_pos; + } + break; + + default: + break; + } + mutex_unlock(&touch->misc_mutex); + + return ret; +} + +static int cyapa_miscdev_rw_params_check(struct cyapa_i2c *touch, + unsigned long offset, unsigned int length) +{ + unsigned int max_offset; + + if (touch == NULL) + return -ENODEV; + + /* + * application may read/write 0 length byte + * to reset read/write pointer to offset. + */ + max_offset = (length == 0) ? offset : (length - 1 + offset); + + /* max registers contained in one register map in bytes is 256. */ + if (cyapa_pos_validate(offset) && cyapa_pos_validate(max_offset)) + return 0; + + pr_debug("invalid parameters, length=%d, offset=0x%x\n", + length, (unsigned int)offset); + + return -EINVAL; +} + +static ssize_t cyapa_misc_read(struct file *file, char __user *usr_buf, + size_t count, loff_t *offset) +{ + int ret; + int reg_len = (int)count; + unsigned long reg_offset = *offset; + char reg_buf[CYAPA_REG_MAP_SIZE]; + struct cyapa_i2c *touch = (struct cyapa_i2c *)file->private_data; + + ret = cyapa_miscdev_rw_params_check(touch, reg_offset, count); + if (ret < 0) + return ret; + + ret = cyapa_i2c_reg_read_block(touch, (u16)reg_offset, + reg_len, reg_buf); + if (ret < 0) { + pr_err("cyapa trackpad I2C read FAILED.\n"); + return ret; + } + + if (ret < reg_len) + pr_warning("Expected %d bytes, read %d bytes.\n", + reg_len, ret); + reg_len = ret; + + if (copy_to_user(usr_buf, reg_buf, reg_len)) { + ret = -EFAULT; + } else { + *offset += reg_len; + ret = reg_len; + } + + return ret; +} + +static ssize_t cyapa_misc_write(struct file *file, const char __user *usr_buf, + size_t count, loff_t *offset) +{ + int ret; + unsigned long reg_offset = *offset; + char reg_buf[CYAPA_REG_MAP_SIZE]; + struct cyapa_i2c *touch = (struct cyapa_i2c *)file->private_data; + + ret = cyapa_miscdev_rw_params_check(touch, reg_offset, count); + if (ret < 0) + return ret; + + if (copy_from_user(reg_buf, usr_buf, (int)count)) { + pr_err("copy data from user space failed.\n"); + return -EINVAL; + } + + ret = cyapa_i2c_reg_write_block(touch, + (u16)reg_offset, + (int)count, + reg_buf); + if (ret < 0) + pr_err("cyapa trackpad I2C write FAILED.\n"); + + *offset = (ret < 0) ? reg_offset : (reg_offset + ret); + + return ret; +} + int cyapa_get_trackpad_run_mode(struct cyapa_i2c *touch, struct cyapa_trackpad_run_mode *run_mode) { @@ -643,6 +859,279 @@ int cyapa_get_trackpad_run_mode(struct cyapa_i2c *touch, return 0; } +static int cyapa_send_mode_switch_cmd(struct cyapa_i2c *touch, + struct cyapa_trackpad_run_mode *run_mode) +{ + int ret; + unsigned long flags; + unsigned short reset_offset; + + if (touch->pdata->gen < CYAPA_GEN3) + return -EINVAL; + reset_offset = CYAPA_GEN3_OFFSET_SOFT_RESET; + + switch (run_mode->rev_cmd) { + case CYAPA_CMD_APP_TO_IDLE: + /* do reset operation to switch to bootloader idle mode. */ + cyapa_bl_disable_irq(touch); + + ret = cyapa_i2c_reg_write_byte(touch, reset_offset, 0x01); + if (ret < 0) { + pr_err("send firmware reset cmd failed, %d\n", + ret); + cyapa_bl_enable_irq(touch); + return -EIO; + } + break; + + case CYAPA_CMD_IDLE_TO_ACTIVE: + cyapa_bl_disable_irq(touch); + /* send switch to active command. */ + ret = cyapa_i2c_reg_write_block(touch, 0, + sizeof(bl_switch_active), bl_switch_active); + if (ret != sizeof(bl_switch_active)) { + pr_err("send active switch cmd failed, %d\n", + ret); + return -EIO; + } + break; + + case CYAPA_CMD_ACTIVE_TO_IDLE: + cyapa_bl_disable_irq(touch); + /* send switch to idle command.*/ + ret = cyapa_i2c_reg_write_block(touch, 0, + sizeof(bl_switch_idle), bl_switch_idle); + if (ret != sizeof(bl_switch_idle)) { + pr_err("send idle switch cmd failed, %d\n", + ret); + return -EIO; + } + break; + + case CYAPA_CMD_IDLE_TO_APP: + /* send command switch operational mode.*/ + ret = cyapa_i2c_reg_write_block(touch, 0, + sizeof(bl_app_launch), bl_app_launch); + if (ret != sizeof(bl_app_launch)) { + pr_err("send applaunch cmd failed, %d\n", + ret); + return -EIO; + } + + /* + * wait firmware completely launched its application, + * during this time, all read/write operations should + * be disabled. + * + * NOTES: + * When trackpad boots for the first time after being + * updating to new firmware, it must first calibrate + * its sensors. + * This sensor calibration takes about 2 seconds to complete. + * This calibration is ONLY required for the first + * post-firmware-update boot. + * + * On all boots the driver waits 300 ms after switching to + * operational mode. + * For the first post-firmware-update boot, + * additional waiting is done in cyapa_i2c_reconfig(). + */ + msleep(300); + + /* update firmware working mode state in driver. */ + spin_lock_irqsave(&touch->miscdev_spinlock, flags); + touch->fw_work_mode = CYAPA_STREAM_MODE; + spin_unlock_irqrestore(&touch->miscdev_spinlock, flags); + + /* reconfig and update firmware information. */ + cyapa_i2c_reconfig(touch, 0); + + cyapa_bl_enable_irq(touch); + + break; + + default: + /* unknown command. */ + return -EINVAL; + } + + /* update firmware working mode state in driver. */ + if (run_mode->rev_cmd != CYAPA_CMD_IDLE_TO_APP) { + spin_lock_irqsave(&touch->miscdev_spinlock, flags); + touch->fw_work_mode = CYAPA_BOOTLOAD_MODE; + spin_unlock_irqrestore(&touch->miscdev_spinlock, flags); + } + + return 0; +} + +static long cyapa_misc_ioctl(struct file *file, unsigned int cmd, + unsigned long arg) +{ + int ret; + int ioctl_len; + struct cyapa_i2c *touch = (struct cyapa_i2c *)file->private_data; + struct cyapa_misc_ioctl_data ioctl_data; + struct cyapa_trackpad_run_mode run_mode; + unsigned char buf[8]; + + if (touch == NULL) { + pr_err("cypress trackpad device does not exist.\n"); + return -ENODEV; + } + + /* copy to kernel space. */ + ioctl_len = sizeof(struct cyapa_misc_ioctl_data); + if (copy_from_user(&ioctl_data, (u8 *)arg, ioctl_len)) + return -EINVAL; + + switch (cmd) { + case CYAPA_GET_PRODUCT_ID: + if (!ioctl_data.buf || ioctl_data.len < 16) + return -EINVAL; + + ret = cyapa_get_query_data(touch); + if (ret < 0) + return ret; + ioctl_data.len = 16; + if (copy_to_user(ioctl_data.buf, touch->product_id, 16)) + return -EIO; + if (copy_to_user((void *)arg, &ioctl_data, ioctl_len)) + return -EIO; + return ioctl_data.len; + + case CYAPA_GET_DRIVER_VER: + if (!ioctl_data.buf || ioctl_data.len < 3) + return -EINVAL; + + ioctl_data.len = 3; + memset(buf, 0, sizeof(buf)); + buf[0] = (unsigned char)CYAPA_MAJOR_VER; + buf[1] = (unsigned char)CYAPA_MINOR_VER; + buf[2] = (unsigned char)CYAPA_REVISION_VER; + if (copy_to_user(ioctl_data.buf, buf, ioctl_data.len)) + return -EIO; + if (copy_to_user((void *)arg, &ioctl_data, ioctl_len)) + return -EIO; + return ioctl_data.len; + + case CYAPA_GET_FIRMWARE_VER: + if (!ioctl_data.buf || ioctl_data.len < 2) + return -EINVAL; + + ret = cyapa_get_query_data(touch); + if (ret < 0) + return ret; + ioctl_data.len = 2; + memset(buf, 0, sizeof(buf)); + buf[0] = touch->fw_maj_ver; + buf[1] = touch->fw_min_ver; + if (copy_to_user(ioctl_data.buf, buf, ioctl_data.len)) + return -EIO; + if (copy_to_user((void *)arg, &ioctl_data, ioctl_len)) + return -EIO; + return ioctl_data.len; + + case CYAPA_GET_HARDWARE_VER: + if (!ioctl_data.buf || ioctl_data.len < 2) + return -EINVAL; + + ret = cyapa_get_query_data(touch); + if (ret < 0) + return ret; + ioctl_data.len = 2; + memset(buf, 0, sizeof(buf)); + buf[0] = touch->hw_maj_ver; + buf[1] = touch->hw_min_ver; + if (copy_to_user(ioctl_data.buf, buf, ioctl_data.len)) + return -EIO; + if (copy_to_user((void *)arg, &ioctl_data, ioctl_len)) + return -EIO; + return ioctl_data.len; + + case CYAPA_GET_PROTOCOL_VER: + if (!ioctl_data.buf || ioctl_data.len < 1) + return -EINVAL; + + if (cyapa_determine_firmware_gen(touch) < 0) + return -EINVAL; + cyapa_get_reg_offset(touch); + ioctl_data.len = 1; + memset(buf, 0, sizeof(buf)); + buf[0] = touch->pdata->gen; + if (copy_to_user(ioctl_data.buf, buf, ioctl_data.len)) + return -EIO; + if (copy_to_user((void *)arg, &ioctl_data, ioctl_len)) + return -EIO; + return ioctl_data.len; + + + case CYAPA_GET_TRACKPAD_RUN_MODE: + if (!ioctl_data.buf || ioctl_data.len < 2) + return -EINVAL; + + /* get trackpad status. */ + ret = cyapa_get_trackpad_run_mode(touch, &run_mode); + if (ret < 0) + return ret; + + ioctl_data.len = 2; + memset(buf, 0, sizeof(buf)); + buf[0] = run_mode.run_mode; + buf[1] = run_mode.bootloader_state; + if (copy_to_user(ioctl_data.buf, buf, ioctl_data.len)) + return -EIO; + + if (copy_to_user((void *)arg, &ioctl_data, ioctl_len)) + return -EIO; + + return ioctl_data.len; + + case CYAYA_SEND_MODE_SWITCH_CMD: + if (!ioctl_data.buf || ioctl_data.len < 3) + return -EINVAL; + + ret = copy_from_user(&run_mode, (u8 *)ioctl_data.buf, + sizeof(struct cyapa_trackpad_run_mode)); + if (ret) + return -EINVAL; + + return cyapa_send_mode_switch_cmd(touch, &run_mode); + + default: + ret = -EINVAL; + break; + } + + return ret; +} + +static const struct file_operations cyapa_misc_fops = { + .owner = THIS_MODULE, + .open = cyapa_misc_open, + .release = cyapa_misc_close, + .unlocked_ioctl = cyapa_misc_ioctl, + .llseek = cyapa_misc_llseek, + .read = cyapa_misc_read, + .write = cyapa_misc_write, +}; + +static struct miscdevice cyapa_misc_dev = { + .name = CYAPA_MISC_NAME, + .fops = &cyapa_misc_fops, + .minor = MISC_DYNAMIC_MINOR, +}; + +static int __init cyapa_misc_init(void) +{ + return misc_register(&cyapa_misc_dev); +} + +static void __exit cyapa_misc_exit(void) +{ + misc_deregister(&cyapa_misc_dev); +} + static void cyapa_update_firmware_dispatch(struct cyapa_i2c *touch) { /* do something here to update trackpad firmware. */ @@ -1797,14 +2286,27 @@ static int __init cyapa_i2c_init(void) int ret; ret = i2c_add_driver(&cypress_i2c_driver); - if (ret) + if (ret) { pr_err("cypress i2c driver register FAILED.\n"); + return ret; + } + + /* + * though misc cyapa interface device initialization may failed, + * but it won't affect the function of trackpad device when + * cypress_i2c_driver initialized successfully. + * misc init failure will only affect firmware upload function, + * so do not check cyapa_misc_init return value here. + */ + cyapa_misc_init(); return ret; } static void __exit cyapa_i2c_exit(void) { + cyapa_misc_exit(); + i2c_del_driver(&cypress_i2c_driver); } -- 1.7.5.1