| /* drivers/input/touchscreen/maxim_sti.c |
| * |
| * Maxim SmartTouch Imager Touchscreen Driver |
| * |
| * Copyright (c)2013 Maxim Integrated Products, Inc. |
| * Copyright (C) 2013, NVIDIA Corporation. All Rights Reserved. |
| * Copyright (c) 2015-2016, The Linux Foundation. All rights reserved. |
| * |
| * This software is licensed under the terms of the GNU General Public |
| * License version 2, as published by the Free Software Foundation, and |
| * may be copied, distributed, and modified under those terms. |
| * |
| * 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. |
| * |
| */ |
| |
| #include <linux/module.h> |
| #include <linux/moduleparam.h> |
| #include <linux/kmod.h> |
| #include <linux/kthread.h> |
| #include <linux/spi/spi.h> |
| #include <linux/firmware.h> |
| #include <linux/crc16.h> |
| #include <linux/interrupt.h> |
| #include <linux/input.h> |
| #include <linux/regulator/consumer.h> |
| #include <net/genetlink.h> |
| #include <net/sock.h> |
| #include <uapi/linux/maxim_sti.h> |
| #include <linux/input/mt.h> |
| #include <asm/byteorder.h> /* MUST include this header to get byte order */ |
| #ifdef CONFIG_OF |
| #include <linux/gpio.h> |
| #include <linux/of_gpio.h> |
| #endif |
| |
| /****************************************************************************\ |
| * Custom features * |
| \****************************************************************************/ |
| |
| #define VTG_DVDD_MIN_UV 1800000 |
| #define VTG_DVDD_MAX_UV 1800000 |
| #define VTG_AVDD_MIN_UV 2850000 |
| #define VTG_AVDD_MAX_UV 2850000 |
| #define INPUT_DEVICES 1 |
| #define CFG_FILE_NAME_MAX 64 |
| |
| #define BUF_SIZE 4100 |
| #define DEF_NL_MC_GROUPS 5 |
| #define DEF_CHIP_ACC_METHOD 1 |
| #define TOUCH_FUSION "touch_fusion" |
| |
| #if defined(CONFIG_FB) |
| #include <linux/fb.h> |
| #endif |
| |
| /****************************************************************************\ |
| * Device context structure, globals, and macros * |
| \****************************************************************************/ |
| #define MAXIM_STI_NAME "maxim_sti" |
| |
| struct maxim_sti_pdata { |
| char *touch_fusion; |
| char *config_file; |
| char *nl_family; |
| char *fw_name; |
| u32 nl_mc_groups; |
| u32 chip_access_method; |
| u32 default_reset_state; |
| u32 tx_buf_size; |
| u32 rx_buf_size; |
| int gpio_reset; |
| int gpio_irq; |
| int (*init)(struct maxim_sti_pdata *pdata, bool init); |
| void (*reset)(struct maxim_sti_pdata *pdata, int value); |
| int (*irq)(struct maxim_sti_pdata *pdata); |
| bool mt_type_b_enabled; |
| }; |
| |
| struct dev_data; |
| |
| struct chip_access_method { |
| int (*read)(struct dev_data *dd, u16 address, u8 *buf, u16 len); |
| int (*write)(struct dev_data *dd, u16 address, u8 *buf, u16 len); |
| }; |
| |
| struct dev_data { |
| u8 *tx_buf; |
| u8 *rx_buf; |
| u8 send_fail_count; |
| u32 nl_seq; |
| u8 nl_mc_group_count; |
| bool nl_enabled; |
| bool start_fusion; |
| bool suspended; |
| bool suspend_in_progress; |
| bool resume_in_progress; |
| bool expect_resume_ack; |
| bool eraser_active; |
| bool legacy_acceleration; |
| bool input_no_deconfig; |
| bool irq_registered; |
| u16 irq_param[MAX_IRQ_PARAMS]; |
| pid_t fusion_process; |
| char input_phys[128]; |
| struct input_dev *input_dev[INPUT_DEVICES]; |
| struct completion suspend_resume; |
| struct chip_access_method chip; |
| struct spi_device *spi; |
| struct genl_family nl_family; |
| struct genl_ops *nl_ops; |
| struct genl_multicast_group *nl_mc_groups; |
| struct sk_buff *outgoing_skb; |
| struct sk_buff_head incoming_skb_queue; |
| struct task_struct *thread; |
| struct sched_param thread_sched; |
| struct list_head dev_list; |
| struct regulator *reg_avdd; |
| struct regulator *reg_dvdd; |
| bool supply; |
| void (*service_irq)(struct dev_data *dd); |
| struct notifier_block fb_notifier; |
| struct kobject *parent; |
| char config_file[CFG_FILE_NAME_MAX]; |
| u8 sysfs_created; |
| u16 chip_id; |
| u16 glove_enabled; |
| u16 charger_mode_en; |
| u16 lcd_fps; |
| u16 tf_ver; |
| u16 drv_ver; |
| u16 fw_ver; |
| u16 fw_protocol; |
| u32 tf_status; |
| bool idle; |
| bool gesture_reset; |
| bool touch_status[INPUT_DEVICES] |
| [MAX_INPUT_EVENTS]; |
| u16 gesture_en; |
| unsigned long int sysfs_update_type; |
| struct completion sysfs_ack_glove; |
| struct completion sysfs_ack_charger; |
| struct completion sysfs_ack_lcd_fps; |
| struct mutex sysfs_update_mutex; |
| struct pinctrl_state *pinctrl_state_active; |
| struct pinctrl_state *pinctrl_state_suspended; |
| struct pinctrl *ts_pinctrl; |
| }; |
| |
| static unsigned short panel_id; |
| static struct list_head dev_list; |
| static spinlock_t dev_lock; |
| |
| static irqreturn_t irq_handler(int irq, void *context); |
| static void service_irq(struct dev_data *dd); |
| static void service_irq_legacy_acceleration(struct dev_data *dd); |
| static int send_sysfs_info(struct dev_data *dd); |
| |
| #define ERROR(a, b...) dev_err(&dd->spi->dev, "maxim: %s (ERROR:%s:%d): " \ |
| a "\n", dd->nl_family.name, __func__, \ |
| __LINE__, ##b) |
| #define INFO(a, b...) dev_info(&dd->spi->dev, "maxim: %s: " a "\n", \ |
| dd->nl_family.name, ##b) |
| #define DBG(a, b...) dev_dbg(&dd->spi->dev, "maxim: %s: " a "\n", \ |
| dd->nl_family.name, ##b) |
| |
| #define DRV_MSG_DBG(...) |
| #define DRV_PT_DBG(...) |
| |
| /****************************************************************************\ |
| * Chip access methods * |
| \****************************************************************************/ |
| |
| static int |
| spi_read_123(struct dev_data *dd, u16 address, u8 *buf, u16 len, bool add_len) |
| { |
| struct spi_message message; |
| struct spi_transfer transfer; |
| u16 *tx_buf = (u16 *)dd->tx_buf; |
| u16 *rx_buf = (u16 *)dd->rx_buf; |
| u16 words = len / sizeof(u16), header_len = 1; |
| u16 *ptr2 = rx_buf + 1; |
| int ret; |
| |
| if (tx_buf == NULL || rx_buf == NULL) |
| return -ENOMEM; |
| |
| tx_buf[0] = (address << 1) | 0x0001; |
| |
| if (add_len) { |
| tx_buf[1] = words; |
| ptr2++; |
| header_len++; |
| } |
| |
| spi_message_init(&message); |
| memset(&transfer, 0, sizeof(transfer)); |
| |
| transfer.len = len + header_len * sizeof(u16); |
| transfer.tx_buf = tx_buf; |
| transfer.rx_buf = rx_buf; |
| spi_message_add_tail(&transfer, &message); |
| |
| do { |
| ret = spi_sync(dd->spi, &message); |
| } while (ret == -EAGAIN); |
| |
| memcpy(buf, ptr2, len); |
| |
| return ret; |
| } |
| |
| static int |
| spi_write_123(struct dev_data *dd, u16 address, u8 *buf, u16 len, |
| bool add_len) |
| { |
| struct maxim_sti_pdata *pdata = dd->spi->dev.platform_data; |
| u16 *tx_buf = (u16 *)dd->tx_buf; |
| u16 words = len / sizeof(u16), header_len = 1; |
| int ret; |
| |
| if (tx_buf == NULL) |
| return -ENOMEM; |
| |
| tx_buf[0] = address << 1; |
| if (add_len) { |
| tx_buf[1] = words; |
| header_len++; |
| } |
| memcpy(tx_buf + header_len, buf, len); |
| |
| do { |
| ret = spi_write(dd->spi, tx_buf, |
| len + header_len * sizeof(u16)); |
| } while (ret == -EAGAIN); |
| |
| memset(dd->tx_buf, 0xFF, pdata->tx_buf_size); |
| return ret; |
| } |
| |
| /* ======================================================================== */ |
| |
| static int |
| spi_read_1(struct dev_data *dd, u16 address, u8 *buf, u16 len) |
| { |
| return spi_read_123(dd, address, buf, len, true); |
| } |
| |
| static int |
| spi_write_1(struct dev_data *dd, u16 address, u8 *buf, u16 len) |
| { |
| return spi_write_123(dd, address, buf, len, true); |
| } |
| |
| /* ======================================================================== */ |
| |
| static inline int |
| stop_legacy_acceleration(struct dev_data *dd) |
| { |
| u16 value = 0xDEAD, status, i; |
| int ret; |
| |
| ret = spi_write_123(dd, 0x0003, (u8 *)&value, |
| sizeof(value), false); |
| if (ret < 0) |
| return -1; |
| usleep_range(100, 120); |
| |
| for (i = 0; i < 200; i++) { |
| ret = spi_read_123(dd, 0x0003, (u8 *)&status, sizeof(status), |
| false); |
| if (ret < 0) |
| return -1; |
| if (status == 0xABCD) |
| return 0; |
| } |
| |
| return -2; |
| } |
| |
| static inline int |
| start_legacy_acceleration(struct dev_data *dd) |
| { |
| u16 value = 0xBEEF; |
| int ret; |
| |
| ret = spi_write_123(dd, 0x0003, (u8 *)&value, sizeof(value), false); |
| usleep_range(100, 120); |
| |
| return ret; |
| } |
| |
| static inline int |
| spi_rw_2_poll_status(struct dev_data *dd) |
| { |
| u16 status, i; |
| int ret; |
| |
| for (i = 0; i < 200; i++) { |
| ret = spi_read_123(dd, 0x0000, (u8 *)&status, sizeof(status), |
| false); |
| if (ret < 0) |
| return -1; |
| if (status == 0xABCD) |
| return 0; |
| } |
| |
| return -2; |
| } |
| |
| static inline int |
| spi_read_2_page(struct dev_data *dd, u16 address, u8 *buf, u16 len) |
| { |
| u16 request[] = {0xFEDC, (address << 1) | 0x0001, len / sizeof(u16)}; |
| int ret; |
| |
| /* write read request header */ |
| ret = spi_write_123(dd, 0x0000, (u8 *)request, sizeof(request), |
| false); |
| if (ret < 0) |
| return -1; |
| |
| /* poll status */ |
| ret = spi_rw_2_poll_status(dd); |
| if (ret < 0) |
| return ret; |
| |
| /* read data */ |
| ret = spi_read_123(dd, 0x0004, (u8 *)buf, len, false); |
| return ret; |
| } |
| |
| static inline int |
| spi_write_2_page(struct dev_data *dd, u16 address, u8 *buf, u16 len) |
| { |
| u16 page[254]; |
| int ret; |
| |
| page[0] = 0xFEDC; |
| page[1] = address << 1; |
| page[2] = len / sizeof(u16); |
| page[3] = 0x0000; |
| memcpy(page + 4, buf, len); |
| |
| /* write data with write request header */ |
| ret = spi_write_123(dd, 0x0000, (u8 *)page, len + 4 * sizeof(u16), |
| false); |
| if (ret < 0) |
| return -1; |
| |
| /* poll status */ |
| return spi_rw_2_poll_status(dd); |
| } |
| |
| static inline int |
| spi_rw_2(struct dev_data *dd, u16 address, u8 *buf, u16 len, |
| int (*func)(struct dev_data *dd, u16 address, u8 *buf, u16 len)) |
| { |
| u16 rx_len, rx_limit = 250 * sizeof(u16), offset = 0; |
| int ret; |
| |
| while (len > 0) { |
| rx_len = (len > rx_limit) ? rx_limit : len; |
| if (dd->legacy_acceleration) |
| stop_legacy_acceleration(dd); |
| ret = func(dd, address + (offset / sizeof(u16)), buf + offset, |
| rx_len); |
| if (dd->legacy_acceleration) |
| start_legacy_acceleration(dd); |
| if (ret < 0) |
| return ret; |
| offset += rx_len; |
| len -= rx_len; |
| } |
| |
| return 0; |
| } |
| |
| static int |
| spi_read_2(struct dev_data *dd, u16 address, u8 *buf, u16 len) |
| { |
| return spi_rw_2(dd, address, buf, len, spi_read_2_page); |
| } |
| |
| static int |
| spi_write_2(struct dev_data *dd, u16 address, u8 *buf, u16 len) |
| { |
| return spi_rw_2(dd, address, buf, len, spi_write_2_page); |
| } |
| |
| /* ======================================================================== */ |
| |
| static int |
| spi_read_3(struct dev_data *dd, u16 address, u8 *buf, u16 len) |
| { |
| return spi_read_123(dd, address, buf, len, false); |
| } |
| |
| static int |
| spi_write_3(struct dev_data *dd, u16 address, u8 *buf, u16 len) |
| { |
| return spi_write_123(dd, address, buf, len, false); |
| } |
| |
| /* ======================================================================== */ |
| |
| static struct chip_access_method chip_access_methods[] = { |
| { |
| .read = spi_read_1, |
| .write = spi_write_1, |
| }, |
| { |
| .read = spi_read_2, |
| .write = spi_write_2, |
| }, |
| { |
| .read = spi_read_3, |
| .write = spi_write_3, |
| }, |
| }; |
| |
| static int |
| set_chip_access_method(struct dev_data *dd, u8 method) |
| { |
| if (method == 0 || method > ARRAY_SIZE(chip_access_methods)) |
| return -1; |
| |
| memcpy(&dd->chip, &chip_access_methods[method - 1], sizeof(dd->chip)); |
| return 0; |
| } |
| |
| /* ======================================================================== */ |
| |
| static inline int |
| stop_legacy_acceleration_canned(struct dev_data *dd) |
| { |
| u16 value = dd->irq_param[18]; |
| |
| return dd->chip.write(dd, dd->irq_param[16], (u8 *)&value, |
| sizeof(value)); |
| } |
| |
| static inline int |
| start_legacy_acceleration_canned(struct dev_data *dd) |
| { |
| u16 value = dd->irq_param[17]; |
| |
| return dd->chip.write(dd, dd->irq_param[16], (u8 *)&value, |
| sizeof(value)); |
| } |
| |
| /* ======================================================================== */ |
| |
| #define FLASH_BLOCK_SIZE 64 /* flash write buffer in words */ |
| #define FIRMWARE_SIZE 0xC000 /* fixed 48Kbytes */ |
| |
| static int bootloader_wait_ready(struct dev_data *dd) |
| { |
| u16 status, i; |
| |
| for (i = 0; i < 15; i++) { |
| if (spi_read_3(dd, 0x00FF, (u8 *)&status, |
| sizeof(status)) != 0) |
| return -1; |
| if (status == 0xABCC) |
| return 0; |
| if (i >= 3) |
| usleep_range(500, 700); |
| } |
| ERROR("unexpected status %04X", status); |
| return -1; |
| } |
| |
| static int bootloader_complete(struct dev_data *dd) |
| { |
| u16 value = 0x5432; |
| |
| return spi_write_3(dd, 0x00FF, (u8 *)&value, sizeof(value)); |
| } |
| |
| static int bootloader_read_data(struct dev_data *dd, u16 *value) |
| { |
| u16 buffer[2]; |
| |
| if (spi_read_3(dd, 0x00FE, (u8 *)buffer, sizeof(buffer)) != 0) |
| return -1; |
| if (buffer[1] != 0xABCC) |
| return -1; |
| |
| *value = buffer[0]; |
| return bootloader_complete(dd); |
| } |
| |
| static int bootloader_write_data(struct dev_data *dd, u16 value) |
| { |
| u16 buffer[2] = {value, 0x5432}; |
| |
| if (bootloader_wait_ready(dd) != 0) |
| return -1; |
| return spi_write_3(dd, 0x00FE, (u8 *)buffer, sizeof(buffer)); |
| } |
| |
| static int bootloader_wait_command(struct dev_data *dd) |
| { |
| u16 value, i; |
| |
| for (i = 0; i < 15; i++) { |
| if (bootloader_read_data(dd, &value) == 0 && value == 0x003E) |
| return 0; |
| if (i >= 3) |
| usleep_range(500, 700); |
| } |
| return -1; |
| } |
| |
| static int bootloader_enter(struct dev_data *dd) |
| { |
| int i; |
| u16 enter[3] = {0x0047, 0x00C7, 0x0007}; |
| |
| for (i = 0; i < 3; i++) { |
| if (spi_write_3(dd, 0x7F00, (u8 *)&enter[i], |
| sizeof(enter[i])) != 0) |
| return -1; |
| } |
| |
| if (bootloader_wait_command(dd) != 0) |
| return -1; |
| return 0; |
| } |
| |
| static int bootloader_exit(struct dev_data *dd) |
| { |
| u16 value = 0x0000; |
| |
| if (bootloader_write_data(dd, 0x0001) != 0) |
| return -1; |
| return spi_write_3(dd, 0x7F00, (u8 *)&value, sizeof(value)); |
| } |
| |
| static int bootloader_get_crc(struct dev_data *dd, u16 *crc16, u16 len) |
| { |
| u16 command[] = {0x0030, 0x0002, 0x0000, 0x0000, len & 0xFF, |
| len >> 8}, value[2], i; |
| |
| for (i = 0; i < ARRAY_SIZE(command); i++) |
| if (bootloader_write_data(dd, command[i]) != 0) |
| return -1; |
| msleep(200); /* wait 200ms for it to get done */ |
| |
| for (i = 0; i < 2; i++) |
| if (bootloader_read_data(dd, &value[i]) != 0) |
| return -1; |
| |
| if (bootloader_wait_command(dd) != 0) |
| return -1; |
| *crc16 = (value[1] << 8) | value[0]; |
| return 0; |
| } |
| |
| static int bootloader_set_byte_mode(struct dev_data *dd) |
| { |
| u16 command[2] = {0x000A, 0x0000}, i; |
| |
| for (i = 0; i < ARRAY_SIZE(command); i++) |
| if (bootloader_write_data(dd, command[i]) != 0) |
| return -1; |
| if (bootloader_wait_command(dd) != 0) |
| return -1; |
| return 0; |
| } |
| |
| static int bootloader_erase_flash(struct dev_data *dd) |
| { |
| if (bootloader_write_data(dd, 0x0002) != 0) |
| return -1; |
| msleep(60); /* wait 60ms */ |
| if (bootloader_wait_command(dd) != 0) |
| return -1; |
| return 0; |
| } |
| |
| static int bootloader_write_flash(struct dev_data *dd, u16 *image, u16 len) |
| { |
| u16 command[] = {0x00F0, 0x0000, len >> 8, 0x0000, 0x0000}; |
| u16 i, buffer[FLASH_BLOCK_SIZE]; |
| |
| for (i = 0; i < ARRAY_SIZE(command); i++) |
| if (bootloader_write_data(dd, command[i]) != 0) |
| return -1; |
| |
| for (i = 0; i < ((len / sizeof(u16)) / FLASH_BLOCK_SIZE); i++) { |
| if (bootloader_wait_ready(dd) != 0) |
| return -1; |
| memcpy(buffer, (void *)(image + i * FLASH_BLOCK_SIZE), |
| sizeof(buffer)); |
| if (spi_write_3(dd, ((i % 2) == 0) ? 0x0000 : 0x0040, |
| (u8 *)buffer, sizeof(buffer)) != 0) |
| return -1; |
| if (bootloader_complete(dd) != 0) |
| return -1; |
| } |
| |
| usleep_range(10000, 11000); |
| if (bootloader_wait_command(dd) != 0) |
| return -1; |
| return 0; |
| } |
| |
| static int device_fw_load(struct dev_data *dd, const struct firmware *fw) |
| { |
| u16 fw_crc16, chip_crc16; |
| |
| fw_crc16 = crc16(0, fw->data, fw->size); |
| INFO("firmware size (%d) CRC16(0x%04X)", (int)fw->size, fw_crc16); |
| if (bootloader_enter(dd) != 0) { |
| ERROR("failed to enter bootloader"); |
| return -1; |
| } |
| if (bootloader_get_crc(dd, &chip_crc16, fw->size) != 0) { |
| ERROR("failed to get CRC16 from the chip"); |
| return -1; |
| } |
| INFO("chip CRC16(0x%04X)", chip_crc16); |
| if (fw_crc16 != chip_crc16) { |
| INFO("will reprogram chip"); |
| if (bootloader_erase_flash(dd) != 0) { |
| ERROR("failed to erase chip flash"); |
| return -1; |
| } |
| INFO("flash erase OK"); |
| if (bootloader_set_byte_mode(dd) != 0) { |
| ERROR("failed to set byte mode"); |
| return -1; |
| } |
| INFO("byte mode OK"); |
| if (bootloader_write_flash(dd, (u16 *)fw->data, |
| fw->size) != 0) { |
| ERROR("failed to write flash"); |
| return -1; |
| } |
| INFO("flash write OK"); |
| if (bootloader_get_crc(dd, &chip_crc16, fw->size) != 0) { |
| ERROR("failed to get CRC16 from the chip"); |
| return -1; |
| } |
| if (fw_crc16 != chip_crc16) { |
| ERROR("failed to verify programming! (0x%04X)", |
| chip_crc16); |
| return -1; |
| } |
| INFO("chip programmed successfully, new chip CRC16(0x%04X)", |
| chip_crc16); |
| } |
| if (bootloader_exit(dd) != 0) { |
| ERROR("failed to exit bootloader"); |
| return -1; |
| } |
| return 0; |
| } |
| |
| static int fw_request_load(struct dev_data *dd) |
| { |
| const struct firmware *fw; |
| int ret; |
| |
| ret = request_firmware(&fw, "maxim_fp35.bin", &dd->spi->dev); |
| if (ret || fw == NULL) { |
| ERROR("firmware request failed (%d,%p)", ret, fw); |
| return -1; |
| } |
| if (fw->size != FIRMWARE_SIZE) { |
| release_firmware(fw); |
| ERROR("incoming firmware is of wrong size (%04X)", |
| (unsigned int)fw->size); |
| return -1; |
| } |
| ret = device_fw_load(dd, fw); |
| if (ret != 0 && bootloader_exit(dd) != 0) |
| ERROR("failed to exit bootloader"); |
| release_firmware(fw); |
| return ret; |
| } |
| |
| /* ======================================================================== */ |
| |
| static void stop_idle_scan(struct dev_data *dd) |
| { |
| u16 value; |
| |
| value = dd->irq_param[13]; |
| (void)dd->chip.write(dd, dd->irq_param[19], |
| (u8 *)&value, sizeof(value)); |
| } |
| |
| static void stop_scan_canned(struct dev_data *dd) |
| { |
| u16 value; |
| u16 i, clear[2] = { 0 }; |
| |
| if (dd->legacy_acceleration) |
| (void)stop_legacy_acceleration_canned(dd); |
| value = dd->irq_param[13]; |
| (void)dd->chip.write(dd, dd->irq_param[12], (u8 *)&value, |
| sizeof(value)); |
| usleep_range(dd->irq_param[15], dd->irq_param[15] + 1000); |
| (void)dd->chip.read(dd, dd->irq_param[0], (u8 *)&clear[0], |
| sizeof(clear[0])); |
| (void)dd->chip.write(dd, dd->irq_param[0], (u8 *)&clear, |
| sizeof(clear)); |
| |
| for (i = 28; i < 32; i++) { |
| value = dd->irq_param[i]; |
| (void)dd->chip.write(dd, dd->irq_param[27], |
| (u8 *)&value, sizeof(value)); |
| } |
| value = dd->irq_param[33]; |
| (void)dd->chip.write(dd, dd->irq_param[32], |
| (u8 *)&value, sizeof(value)); |
| usleep_range(500, 1000); |
| for (i = 28; i < 32; i++) { |
| value = dd->irq_param[i]; |
| (void)dd->chip.write(dd, dd->irq_param[27], |
| (u8 *)&value, sizeof(value)); |
| } |
| value = dd->irq_param[13]; |
| (void)dd->chip.write(dd, dd->irq_param[32], |
| (u8 *)&value, sizeof(value)); |
| } |
| |
| static void start_scan_canned(struct dev_data *dd) |
| { |
| u16 value; |
| |
| if (dd->legacy_acceleration) { |
| (void)start_legacy_acceleration_canned(dd); |
| } else { |
| value = dd->irq_param[14]; |
| (void)dd->chip.write(dd, dd->irq_param[12], (u8 *)&value, |
| sizeof(value)); |
| } |
| } |
| |
| static void enable_gesture(struct dev_data *dd) |
| { |
| u16 value; |
| |
| if (!dd->idle) |
| stop_scan_canned(dd); |
| |
| value = dd->irq_param[13]; |
| (void)dd->chip.write(dd, dd->irq_param[34], |
| (u8 *)&value, sizeof(value)); |
| value = dd->irq_param[36]; |
| (void)dd->chip.write(dd, dd->irq_param[35], |
| (u8 *)&value, sizeof(value)); |
| |
| value = dd->irq_param[18]; |
| (void)dd->chip.write(dd, dd->irq_param[16], (u8 *)&value, |
| sizeof(value)); |
| |
| value = dd->irq_param[20]; |
| (void)dd->chip.write(dd, dd->irq_param[19], (u8 *)&value, |
| sizeof(value)); |
| if (!dd->idle) { |
| value = dd->irq_param[26]; |
| (void)dd->chip.write(dd, dd->irq_param[25], (u8 *)&value, |
| sizeof(value)); |
| } |
| } |
| |
| static void gesture_wake_detect(struct dev_data *dd) |
| { |
| u16 status, value; |
| int ret = 0; |
| |
| ret = dd->chip.read(dd, dd->irq_param[0], |
| (u8 *)&status, sizeof(status)); |
| INFO("%s: %d = %04X", __func__, dd->irq_param[0], status); |
| if (status & dd->irq_param[10]) |
| dd->gesture_reset = true; |
| if (status & dd->irq_param[18] && dd->input_dev[INPUT_DEVICES - 1]) { |
| ret = dd->chip.read(dd, dd->irq_param[22], |
| (u8 *)&value, sizeof(value)); |
| INFO("gesture code = 0x%04X", value); |
| if (value == dd->irq_param[24]) { |
| INFO("gesture detected"); |
| input_report_key(dd->input_dev[INPUT_DEVICES - 1], KEY_POWER, 1); |
| input_sync(dd->input_dev[INPUT_DEVICES - 1]); |
| input_report_key(dd->input_dev[INPUT_DEVICES - 1], KEY_POWER, 0); |
| input_sync(dd->input_dev[INPUT_DEVICES - 1]); |
| } |
| value = dd->irq_param[18]; |
| ret = dd->chip.write(dd, dd->irq_param[0], |
| (u8 *)&value, sizeof(value)); |
| } else { |
| ret = dd->chip.write(dd, dd->irq_param[0], |
| (u8 *)&status, sizeof(status)); |
| } |
| } |
| |
| static void finish_gesture(struct dev_data *dd) |
| { |
| struct maxim_sti_pdata *pdata = dd->spi->dev.platform_data; |
| u16 value; |
| u8 fail_count = 0; |
| int ret; |
| |
| value = dd->irq_param[21]; |
| (void)dd->chip.write(dd, dd->irq_param[19], (u8 *)&value, |
| sizeof(value)); |
| do { |
| msleep(20); |
| ret = dd->chip.read(dd, dd->irq_param[25], (u8 *)&value, |
| sizeof(value)); |
| if (ret < 0 || fail_count >= 20) { |
| ERROR("failed to read control register (%d)", ret); |
| pdata->reset(pdata, 0); |
| msleep(20); |
| pdata->reset(pdata, 1); |
| return; |
| } |
| INFO("ctrl = 0x%04X", value); |
| fail_count++; |
| } while (value); |
| } |
| |
| static int regulator_init(struct dev_data *dd) |
| { |
| int ret; |
| |
| dd->reg_avdd = devm_regulator_get(&dd->spi->dev, "avdd"); |
| if (IS_ERR(dd->reg_avdd)) { |
| ret = PTR_ERR(dd->reg_avdd); |
| dd->reg_avdd = NULL; |
| dev_err(&dd->spi->dev, "can't find avdd regulator: %d\n", |
| ret); |
| return ret; |
| } |
| |
| ret = regulator_set_voltage(dd->reg_avdd, |
| VTG_AVDD_MIN_UV, VTG_AVDD_MAX_UV); |
| if (ret) { |
| dev_err(&dd->spi->dev, |
| "can't set avdd regulator voltage: %d\n", ret); |
| goto err_free_avdd; |
| } |
| |
| dd->reg_dvdd = devm_regulator_get(&dd->spi->dev, "dvdd"); |
| if (IS_ERR(dd->reg_dvdd)) { |
| ret = PTR_ERR(dd->reg_avdd); |
| dd->reg_dvdd = NULL; |
| dev_err(&dd->spi->dev, "can't find avdd regulator: %d\n", |
| ret); |
| goto err_free_avdd; |
| } |
| |
| ret = regulator_set_voltage(dd->reg_dvdd, |
| VTG_DVDD_MIN_UV, VTG_DVDD_MAX_UV); |
| if (ret) { |
| dev_err(&dd->spi->dev, |
| "can't set dvdd regulator voltage: %d\n", ret); |
| goto err_free_dvdd; |
| } |
| return 0; |
| |
| err_free_dvdd: |
| devm_regulator_put(dd->reg_dvdd); |
| dd->reg_dvdd = NULL; |
| err_free_avdd: |
| devm_regulator_put(dd->reg_avdd); |
| dd->reg_avdd = NULL; |
| return ret; |
| } |
| |
| static void regulator_uninit(struct dev_data *dd) |
| { |
| if (dd->reg_dvdd) |
| devm_regulator_put(dd->reg_dvdd); |
| if (dd->reg_avdd) |
| devm_regulator_put(dd->reg_avdd); |
| } |
| |
| static int regulator_control(struct dev_data *dd, bool on) |
| { |
| int ret; |
| |
| if (!dd->reg_avdd || !dd->reg_dvdd) |
| return 0; |
| |
| if (on && !dd->supply) { |
| DBG("regulator ON."); |
| ret = regulator_enable(dd->reg_dvdd); |
| if (ret < 0) { |
| ERROR("failed to enable dvdd regulator: %d", ret); |
| return ret; |
| } |
| usleep_range(1000, 1020); |
| |
| ret = regulator_enable(dd->reg_avdd); |
| if (ret < 0) { |
| ERROR("failed to enable avdd regulator: %d", ret); |
| regulator_disable(dd->reg_dvdd); |
| return ret; |
| } |
| |
| dd->supply = true; |
| } else if (!on && dd->supply) { |
| DBG("regulator OFF."); |
| |
| ret = regulator_disable(dd->reg_avdd); |
| if (ret < 0) |
| ERROR("Failed to disable regulator avdd: %d", ret); |
| |
| ret = regulator_disable(dd->reg_dvdd); |
| if (ret < 0) |
| ERROR("Failed to disable regulator dvdd: %d", ret); |
| |
| dd->supply = false; |
| } |
| |
| return 0; |
| } |
| |
| #define MAXIM_STI_GPIO_ERROR(ret, gpio, op) \ |
| if (ret < 0) { \ |
| pr_err("%s: GPIO %d %s failed (%d)\n", __func__, gpio, op, \ |
| ret); \ |
| return ret; \ |
| } |
| |
| |
| int maxim_sti_gpio_init(struct maxim_sti_pdata *pdata, bool init) |
| { |
| int ret; |
| |
| if (init) { |
| ret = gpio_request(pdata->gpio_irq, "maxim_sti_irq"); |
| MAXIM_STI_GPIO_ERROR(ret, pdata->gpio_irq, "request"); |
| ret = gpio_direction_input(pdata->gpio_irq); |
| MAXIM_STI_GPIO_ERROR(ret, pdata->gpio_irq, "direction"); |
| ret = gpio_request(pdata->gpio_reset, "maxim_sti_reset"); |
| MAXIM_STI_GPIO_ERROR(ret, pdata->gpio_reset, "request"); |
| ret = gpio_direction_output(pdata->gpio_reset, |
| pdata->default_reset_state); |
| MAXIM_STI_GPIO_ERROR(ret, pdata->gpio_reset, "direction"); |
| } else { |
| gpio_free(pdata->gpio_irq); |
| gpio_free(pdata->gpio_reset); |
| } |
| |
| return 0; |
| } |
| |
| void maxim_sti_gpio_reset(struct maxim_sti_pdata *pdata, int value) |
| { |
| gpio_set_value(pdata->gpio_reset, !!value); |
| } |
| |
| int maxim_sti_gpio_irq(struct maxim_sti_pdata *pdata) |
| { |
| return gpio_get_value(pdata->gpio_irq); |
| } |
| |
| /****************************************************************************\ |
| * Device Tree Support |
| \****************************************************************************/ |
| |
| #ifdef CONFIG_OF |
| static int maxim_parse_dt(struct device *dev, struct maxim_sti_pdata *pdata) |
| { |
| struct device_node *np = dev->of_node; |
| u32 flags; |
| const char *str; |
| int ret; |
| |
| pdata->gpio_reset = of_get_named_gpio_flags(np, |
| "maxim_sti,reset-gpio", 0, &flags); |
| pdata->default_reset_state = (u8)flags; |
| |
| pdata->gpio_irq = of_get_named_gpio_flags(np, |
| "maxim_sti,irq-gpio", 0, &flags); |
| |
| pdata->mt_type_b_enabled = |
| of_property_read_bool(np, "maxim_sti,mt_type_b_enabled"); |
| |
| ret = of_property_read_string(np, "maxim_sti,touch_fusion", &str); |
| if (ret) { |
| dev_err(dev, "%s: unable to read touch_fusion location (%d)\n", |
| __func__, ret); |
| goto fail; |
| } |
| pdata->touch_fusion = (char *)str; |
| |
| ret = of_property_read_string(np, "maxim_sti,config_file", &str); |
| if (ret) { |
| dev_err(dev, "%s: unable to read config_file location (%d)\n", |
| __func__, ret); |
| goto fail; |
| } |
| pdata->config_file = (char *)str; |
| |
| ret = of_property_read_string(np, "maxim_sti,fw_name", &str); |
| if (ret) { |
| dev_err(dev, "%s: unable to read fw_name (%d)\n", |
| __func__, ret); |
| } |
| pdata->fw_name = (char *)str; |
| |
| return 0; |
| |
| fail: |
| return ret; |
| } |
| #endif |
| |
| /****************************************************************************\ |
| * Suspend/resume processing * |
| \****************************************************************************/ |
| |
| #ifdef CONFIG_PM_SLEEP |
| static int suspend(struct device *dev) |
| { |
| struct dev_data *dd = spi_get_drvdata(to_spi_device(dev)); |
| |
| DBG("%s: suspending", __func__); |
| |
| if (dd->suspended) { |
| DBG("%s: already suspended", __func__); |
| return 0; |
| } |
| |
| if (dd->suspend_in_progress) { |
| dev_err(dev, "suspend already in progress!"); |
| return -EINVAL; |
| } |
| |
| dd->suspend_in_progress = true; |
| wake_up_process(dd->thread); |
| wait_for_completion(&dd->suspend_resume); |
| |
| DBG("%s: suspended", __func__); |
| |
| return 0; |
| } |
| |
| static int resume(struct device *dev) |
| { |
| struct dev_data *dd = spi_get_drvdata(to_spi_device(dev)); |
| |
| DBG("%s: resuming", __func__); |
| |
| if (!dd->suspended && !dd->suspend_in_progress) { |
| DBG("%s: not suspended", __func__); |
| return 0; |
| } |
| |
| dd->resume_in_progress = true; |
| wake_up_process(dd->thread); |
| wait_for_completion(&dd->suspend_resume); |
| DBG("%s: resumed", __func__); |
| return 0; |
| } |
| |
| static const struct dev_pm_ops pm_ops = { |
| .suspend = suspend, |
| .resume = resume, |
| }; |
| |
| #if defined(CONFIG_FB) |
| static int fb_notifier_callback(struct notifier_block *self, |
| unsigned long event, void *data) |
| { |
| struct fb_event *evdata = data; |
| int *blank; |
| struct dev_data *dd = container_of(self, |
| struct dev_data, fb_notifier); |
| |
| DBG("%s: event = %lu", __func__, event); |
| if (evdata && evdata->data && event == FB_EVENT_BLANK) { |
| blank = evdata->data; |
| if (*blank == FB_BLANK_UNBLANK) |
| resume(&dd->spi->dev); |
| else if (*blank == FB_BLANK_POWERDOWN) |
| suspend(&dd->spi->dev); |
| } |
| return 0; |
| } |
| #endif |
| #endif |
| |
| /****************************************************************************\ |
| * Netlink processing * |
| \****************************************************************************/ |
| |
| static inline int |
| nl_msg_new(struct dev_data *dd, u8 dst) |
| { |
| dd->outgoing_skb = alloc_skb(NL_BUF_SIZE, GFP_KERNEL); |
| if (dd->outgoing_skb == NULL) |
| return -ENOMEM; |
| nl_msg_init(dd->outgoing_skb->data, dd->nl_family.id, dd->nl_seq++, |
| dst); |
| if (dd->nl_seq == 0) |
| dd->nl_seq++; |
| return 0; |
| } |
| |
| static int |
| nl_callback_noop(struct sk_buff *skb, struct genl_info *info) |
| { |
| return 0; |
| } |
| |
| static void |
| release_slot_events(struct dev_data *dd, bool *active_touch, int inp) |
| { |
| int i; |
| |
| for (i = 0; i < MAX_INPUT_EVENTS; i++) { |
| if (dd->touch_status[inp][i] == true && |
| active_touch[i] == false) { |
| input_mt_slot(dd->input_dev[inp], i); |
| input_mt_report_slot_state(dd->input_dev[inp], |
| MT_TOOL_FINGER, 0); |
| } |
| dd->touch_status[inp][i] = active_touch[i]; |
| } |
| } |
| |
| static inline bool |
| nl_process_driver_msg(struct dev_data *dd, u16 msg_id, void *msg) |
| { |
| struct maxim_sti_pdata *pdata = dd->spi->dev.platform_data; |
| struct dr_echo_request *echo_msg; |
| struct fu_echo_response *echo_response; |
| struct dr_chip_read *read_msg; |
| struct fu_chip_read_result *read_result; |
| struct dr_chip_write *write_msg; |
| struct dr_chip_access_method *chip_access_method_msg; |
| struct dr_delay *delay_msg; |
| struct fu_irqline_status *irqline_status; |
| struct dr_config_irq *config_irq_msg; |
| struct dr_config_input *config_input_msg; |
| struct dr_config_watchdog *config_watchdog_msg; |
| struct dr_input *input_msg; |
| struct dr_legacy_acceleration *legacy_acceleration_msg; |
| struct dr_handshake *handshake_msg; |
| struct fu_handshake_response *handshake_response; |
| struct dr_config_fw *config_fw_msg; |
| struct dr_sysfs_ack *sysfs_ack_msg; |
| struct dr_idle *idle_msg; |
| struct dr_tf_status *tf_status_msg; |
| u8 i, inp; |
| int ret, id; |
| u16 read_value[2] = { 0 }; |
| bool active_touch[INPUT_DEVICES][MAX_INPUT_EVENTS] = { {false} }; |
| |
| if (dd->expect_resume_ack && msg_id != DR_DECONFIG && |
| msg_id != DR_RESUME_ACK) |
| return false; |
| |
| switch (msg_id) { |
| case DR_ADD_MC_GROUP: |
| INFO("warning: deprecated dynamic mc group add request!"); |
| return false; |
| case DR_ECHO_REQUEST: |
| DRV_MSG_DBG("msg: %s", "DR_ECHO_REQUEST"); |
| echo_msg = msg; |
| echo_response = nl_alloc_attr(dd->outgoing_skb->data, |
| FU_ECHO_RESPONSE, |
| sizeof(*echo_response)); |
| if (echo_response == NULL) |
| goto alloc_attr_failure; |
| echo_response->cookie = echo_msg->cookie; |
| return true; |
| case DR_CHIP_READ: |
| DRV_MSG_DBG("msg: %s", "DR_CHIP_READ"); |
| read_msg = msg; |
| read_result = nl_alloc_attr(dd->outgoing_skb->data, |
| FU_CHIP_READ_RESULT, |
| sizeof(*read_result) + read_msg->length); |
| if (read_result == NULL) |
| goto alloc_attr_failure; |
| read_result->address = read_msg->address; |
| read_result->length = read_msg->length; |
| ret = dd->chip.read(dd, read_msg->address, read_result->data, |
| read_msg->length); |
| if (ret < 0) |
| ERROR("failed to read from chip (%d, %d, %d)", |
| read_msg->address, read_msg->length, ret); |
| return true; |
| case DR_CHIP_WRITE: |
| DRV_MSG_DBG("msg: %s", "DR_CHIP_WRITE"); |
| write_msg = msg; |
| if (write_msg->address == dd->irq_param[12] && |
| write_msg->data[0] == dd->irq_param[13]) { |
| ret = dd->chip.write(dd, write_msg->address, |
| write_msg->data, |
| write_msg->length); |
| if (ret < 0) |
| ERROR("failed to write chip (%d)", ret); |
| msleep(15); |
| ret = dd->chip.read(dd, dd->irq_param[0], |
| (u8 *)read_value, |
| sizeof(read_value)); |
| if (ret < 0) |
| ERROR("failed to read from chip (%d)", ret); |
| ret = dd->chip.write(dd, dd->irq_param[0], |
| (u8 *)read_value, |
| sizeof(read_value)); |
| return false; |
| } |
| if (write_msg->address == dd->irq_param[0]) { |
| if (((u16 *)write_msg->data)[0] == dd->irq_param[11]) { |
| ret = dd->chip.read(dd, dd->irq_param[0], |
| (u8 *)read_value, |
| sizeof(read_value)); |
| if (ret < 0) |
| ERROR("failed to read from chip (%d)", ret); |
| ret = dd->chip.write(dd, dd->irq_param[0], |
| (u8 *)read_value, |
| sizeof(read_value)); |
| return false; |
| } |
| read_value[0] = ((u16 *)write_msg->data)[0]; |
| ret = dd->chip.write(dd, write_msg->address, |
| (u8 *)read_value, |
| sizeof(read_value)); |
| return false; |
| } |
| ret = dd->chip.write(dd, write_msg->address, write_msg->data, |
| write_msg->length); |
| if (ret < 0) |
| ERROR("failed to write chip (%d, %d, %d)", |
| write_msg->address, write_msg->length, ret); |
| return false; |
| case DR_CHIP_RESET: |
| DRV_MSG_DBG("msg: %s", "DR_CHIP_RESET"); |
| pdata->reset(pdata, ((struct dr_chip_reset *)msg)->state); |
| return false; |
| case DR_GET_IRQLINE: |
| DRV_MSG_DBG("msg: %s", "DR_GET_IRQLINE"); |
| irqline_status = nl_alloc_attr(dd->outgoing_skb->data, |
| FU_IRQLINE_STATUS, |
| sizeof(*irqline_status)); |
| if (irqline_status == NULL) |
| goto alloc_attr_failure; |
| irqline_status->status = pdata->irq(pdata); |
| return true; |
| case DR_DELAY: |
| DRV_MSG_DBG("msg: %s", "DR_DELAY"); |
| delay_msg = msg; |
| if (delay_msg->period > 1000) |
| msleep(delay_msg->period / 1000); |
| usleep_range(delay_msg->period % 1000, |
| (delay_msg->period % 1000) + 10); |
| return false; |
| case DR_CHIP_ACCESS_METHOD: |
| DRV_MSG_DBG("msg: %s", "DR_CHIP_ACCESS_METHOD"); |
| chip_access_method_msg = msg; |
| ret = set_chip_access_method(dd, |
| chip_access_method_msg->method); |
| if (ret < 0) |
| ERROR("failed to set chip access method (%d) (%d)", |
| ret, chip_access_method_msg->method); |
| return false; |
| case DR_CONFIG_IRQ: |
| DRV_MSG_DBG("msg: %s", "DR_CONFIG_IRQ"); |
| config_irq_msg = msg; |
| if (config_irq_msg->irq_params > MAX_IRQ_PARAMS) { |
| ERROR("too many IRQ parameters"); |
| return false; |
| } |
| memcpy(dd->irq_param, config_irq_msg->irq_param, |
| config_irq_msg->irq_params * sizeof(dd->irq_param[0])); |
| if (dd->irq_registered) |
| return false; |
| dd->service_irq = service_irq; |
| ret = request_irq(dd->spi->irq, irq_handler, |
| (config_irq_msg->irq_edge == DR_IRQ_RISING_EDGE) ? |
| IRQF_TRIGGER_RISING : IRQF_TRIGGER_FALLING, |
| pdata->nl_family, dd); |
| if (ret < 0) { |
| ERROR("failed to request IRQ (%d)", ret); |
| } else { |
| dd->irq_registered = true; |
| wake_up_process(dd->thread); |
| } |
| return false; |
| case DR_CONFIG_INPUT: |
| DRV_MSG_DBG("msg: %s", "DR_CONFIG_INPUT"); |
| config_input_msg = msg; |
| for (i = 0; i < INPUT_DEVICES; i++) |
| if (dd->input_dev[i] != NULL) |
| return false; |
| for (i = 0; i < INPUT_DEVICES; i++) { |
| dd->input_dev[i] = input_allocate_device(); |
| if (dd->input_dev[i] == NULL) { |
| ERROR("failed to allocate input device"); |
| continue; |
| } |
| snprintf(dd->input_phys, sizeof(dd->input_phys), |
| "%s/input%d", dev_name(&dd->spi->dev), i); |
| dd->input_dev[i]->name = pdata->nl_family; |
| dd->input_dev[i]->phys = dd->input_phys; |
| dd->input_dev[i]->id.bustype = BUS_SPI; |
| __set_bit(EV_SYN, dd->input_dev[i]->evbit); |
| __set_bit(EV_ABS, dd->input_dev[i]->evbit); |
| __set_bit(INPUT_PROP_DIRECT, dd->input_dev[i]->propbit); |
| if (i == (INPUT_DEVICES - 1)) { |
| __set_bit(EV_KEY, dd->input_dev[i]->evbit); |
| __set_bit(BTN_TOOL_RUBBER, |
| dd->input_dev[i]->keybit); |
| __set_bit(KEY_POWER, |
| dd->input_dev[i]->keybit); |
| } |
| if (pdata->mt_type_b_enabled && |
| input_mt_init_slots(dd->input_dev[i], |
| MAX_INPUT_EVENTS, 0)) { |
| ERROR("Error in initialising slots\n"); |
| input_free_device(dd->input_dev[i]); |
| dd->input_dev[i] = NULL; |
| continue; |
| } else { |
| input_set_abs_params(dd->input_dev[i], |
| ABS_MT_TRACKING_ID, 0, |
| MAX_INPUT_EVENTS, 0, 0); |
| } |
| |
| input_set_abs_params(dd->input_dev[i], |
| ABS_MT_POSITION_X, 0, |
| config_input_msg->x_range, 0, 0); |
| input_set_abs_params(dd->input_dev[i], |
| ABS_MT_POSITION_Y, 0, |
| config_input_msg->y_range, 0, 0); |
| input_set_abs_params(dd->input_dev[i], |
| ABS_MT_PRESSURE, 0, 0xFF, 0, 0); |
| |
| if (i == (INPUT_DEVICES - 1)) |
| input_set_abs_params(dd->input_dev[i], |
| ABS_MT_TOOL_TYPE, 0, |
| MT_TOOL_MAX, 0, 0); |
| else |
| input_set_abs_params(dd->input_dev[i], |
| ABS_MT_TOOL_TYPE, 0, |
| MT_TOOL_FINGER, 0, 0); |
| |
| ret = input_register_device(dd->input_dev[i]); |
| if (ret < 0) { |
| input_free_device(dd->input_dev[i]); |
| dd->input_dev[i] = NULL; |
| ERROR("failed to register input device"); |
| } |
| } |
| #if defined(CONFIG_FB) |
| dd->fb_notifier.notifier_call = fb_notifier_callback; |
| fb_register_client(&dd->fb_notifier); |
| #endif |
| /* create symlink */ |
| if (dd->parent == NULL && dd->input_dev[0] != NULL) { |
| dd->parent = dd->input_dev[0]->dev.kobj.parent; |
| ret = sysfs_create_link(dd->parent, &dd->spi->dev.kobj, |
| MAXIM_STI_NAME); |
| if (ret) { |
| ERROR("sysfs_create_link error\n"); |
| dd->parent = NULL; |
| } |
| } |
| return false; |
| case DR_CONFIG_WATCHDOG: |
| DRV_MSG_DBG("msg: %s", "DR_CONFIG_WATCHDOG"); |
| config_watchdog_msg = msg; |
| dd->fusion_process = (pid_t)config_watchdog_msg->pid; |
| return false; |
| case DR_DECONFIG: |
| DRV_MSG_DBG("msg: %s", "DR_DECONFIG"); |
| if (dd->irq_registered) { |
| free_irq(dd->spi->irq, dd); |
| dd->irq_registered = false; |
| } |
| stop_scan_canned(dd); |
| |
| if (!dd->input_no_deconfig) { |
| if (dd->parent != NULL) { |
| sysfs_remove_link( |
| dd->input_dev[0]->dev.kobj.parent, |
| MAXIM_STI_NAME); |
| dd->parent = NULL; |
| } |
| } |
| |
| if (!dd->input_no_deconfig) { |
| for (i = 0; i < INPUT_DEVICES; i++) { |
| if (dd->input_dev[i] == NULL) |
| continue; |
| input_unregister_device(dd->input_dev[i]); |
| dd->input_dev[i] = NULL; |
| } |
| #if defined(CONFIG_FB) |
| fb_unregister_client(&dd->fb_notifier); |
| #endif |
| } |
| dd->expect_resume_ack = false; |
| dd->eraser_active = false; |
| dd->legacy_acceleration = false; |
| dd->service_irq = service_irq; |
| dd->fusion_process = (pid_t)0; |
| dd->sysfs_update_type = DR_SYSFS_UPDATE_NONE; |
| return false; |
| case DR_INPUT: |
| DRV_MSG_DBG("msg: %s", "DR_INPUT"); |
| input_msg = msg; |
| if (input_msg->events == 0) { |
| if (dd->eraser_active) { |
| input_report_key( |
| dd->input_dev[INPUT_DEVICES - 1], |
| BTN_TOOL_RUBBER, 0); |
| dd->eraser_active = false; |
| } |
| for (i = 0; i < INPUT_DEVICES; i++) { |
| if (pdata->mt_type_b_enabled) |
| release_slot_events(dd, |
| active_touch[i], i); |
| else |
| input_mt_sync(dd->input_dev[i]); |
| |
| input_sync(dd->input_dev[i]); |
| } |
| } else { |
| for (i = 0; i < input_msg->events; i++) { |
| switch (input_msg->event[i].tool_type) { |
| case DR_INPUT_FINGER: |
| inp = 0; |
| id = input_msg->event[i].id; |
| if (pdata->mt_type_b_enabled) { |
| input_mt_slot( |
| dd->input_dev[inp], id); |
| input_mt_report_slot_state( |
| dd->input_dev[inp], |
| MT_TOOL_FINGER, 1); |
| active_touch[inp][id] = true; |
| } else { |
| input_report_abs( |
| dd->input_dev[inp], |
| ABS_MT_TOOL_TYPE, |
| MT_TOOL_FINGER); |
| } |
| break; |
| case DR_INPUT_STYLUS: |
| inp = INPUT_DEVICES - 1; |
| if (pdata->mt_type_b_enabled) { |
| input_mt_slot( |
| dd->input_dev[inp], id); |
| input_mt_report_slot_state( |
| dd->input_dev[inp], |
| MT_TOOL_PEN, 1); |
| active_touch[inp][id] = true; |
| } else { |
| input_report_abs( |
| dd->input_dev[inp], |
| ABS_MT_TOOL_TYPE, |
| MT_TOOL_PEN); |
| } |
| break; |
| case DR_INPUT_ERASER: |
| inp = INPUT_DEVICES - 1; |
| input_report_key(dd->input_dev[inp], |
| BTN_TOOL_RUBBER, 1); |
| dd->eraser_active = true; |
| break; |
| default: |
| inp = 0; |
| ERROR("invalid input tool type (%d)", |
| input_msg->event[i].tool_type); |
| break; |
| } |
| if (!pdata->mt_type_b_enabled) |
| input_report_abs(dd->input_dev[inp], |
| ABS_MT_TRACKING_ID, id); |
| input_report_abs(dd->input_dev[inp], |
| ABS_MT_POSITION_X, |
| input_msg->event[i].x); |
| input_report_abs(dd->input_dev[inp], |
| ABS_MT_POSITION_Y, |
| input_msg->event[i].y); |
| input_report_abs(dd->input_dev[inp], |
| ABS_MT_PRESSURE, |
| input_msg->event[i].z); |
| if (!pdata->mt_type_b_enabled) |
| input_mt_sync(dd->input_dev[inp]); |
| } |
| |
| if (pdata->mt_type_b_enabled) { |
| for (i = 0; i < INPUT_DEVICES; i++) |
| release_slot_events(dd, |
| active_touch[i], i); |
| } |
| |
| for (i = 0; i < INPUT_DEVICES; i++) |
| input_sync(dd->input_dev[i]); |
| } |
| return false; |
| case DR_RESUME_ACK: |
| DRV_MSG_DBG("msg: %s", "DR_RESUME_ACK"); |
| dd->expect_resume_ack = false; |
| if (dd->irq_registered |
| && !dd->gesture_en |
| ) |
| enable_irq(dd->spi->irq); |
| return false; |
| case DR_LEGACY_FWDL: |
| DRV_MSG_DBG("msg: %s", "DR_LEGACY_FWDL"); |
| ret = fw_request_load(dd); |
| if (ret < 0) |
| ERROR("firmware download failed (%d)", ret); |
| else |
| INFO("firmware download OK"); |
| return false; |
| case DR_LEGACY_ACCELERATION: |
| DRV_MSG_DBG("msg: %s", "DR_LEGACY_ACCELERATION"); |
| legacy_acceleration_msg = msg; |
| if (legacy_acceleration_msg->enable) { |
| dd->service_irq = service_irq_legacy_acceleration; |
| start_legacy_acceleration(dd); |
| dd->legacy_acceleration = true; |
| } else { |
| stop_legacy_acceleration(dd); |
| dd->legacy_acceleration = false; |
| dd->service_irq = service_irq; |
| } |
| return false; |
| case DR_HANDSHAKE: |
| DRV_MSG_DBG("msg: %s", "DR_HANDSHAKE"); |
| handshake_msg = msg; |
| dd->tf_ver = handshake_msg->tf_ver; |
| dd->chip_id = handshake_msg->chip_id; |
| dd->drv_ver = DRIVER_VERSION_NUM; |
| handshake_response = nl_alloc_attr(dd->outgoing_skb->data, |
| FU_HANDSHAKE_RESPONSE, |
| sizeof(*handshake_response)); |
| if (handshake_response == NULL) |
| goto alloc_attr_failure; |
| handshake_response->driver_ver = dd->drv_ver; |
| handshake_response->panel_id = panel_id; |
| handshake_response->driver_protocol = DRIVER_PROTOCOL; |
| return true; |
| case DR_CONFIG_FW: |
| DRV_MSG_DBG("msg: %s", "DR_CONFIG_FW"); |
| config_fw_msg = msg; |
| dd->fw_ver = config_fw_msg->fw_ver; |
| dd->fw_protocol = config_fw_msg->fw_protocol; |
| return false; |
| case DR_SYSFS_ACK: |
| sysfs_ack_msg = msg; |
| if (sysfs_ack_msg->type == DR_SYSFS_ACK_GLOVE) |
| complete(&dd->sysfs_ack_glove); |
| if (sysfs_ack_msg->type == DR_SYSFS_ACK_CHARGER) |
| complete(&dd->sysfs_ack_charger); |
| if (sysfs_ack_msg->type == DR_SYSFS_ACK_LCD_FPS) |
| complete(&dd->sysfs_ack_lcd_fps); |
| return false; |
| case DR_IDLE: |
| DRV_MSG_DBG("msg: %s", "DR_IDLE"); |
| idle_msg = msg; |
| dd->idle = !!(idle_msg->idle); |
| return false; |
| case DR_TF_STATUS: |
| tf_status_msg = msg; |
| dd->tf_status = tf_status_msg->tf_status; |
| return false; |
| default: |
| ERROR("unexpected message %d", msg_id); |
| return false; |
| } |
| |
| alloc_attr_failure: |
| ERROR("failed to allocate response for msg_id %d", msg_id); |
| return false; |
| } |
| |
| static int nl_process_msg(struct dev_data *dd, struct sk_buff *skb) |
| { |
| struct nlattr *attr; |
| bool send_reply = false; |
| int ret = 0, ret2; |
| |
| /* process incoming message */ |
| attr = NL_ATTR_FIRST(skb->data); |
| for (; attr < NL_ATTR_LAST(skb->data); attr = NL_ATTR_NEXT(attr)) { |
| if (nl_process_driver_msg(dd, attr->nla_type, |
| NL_ATTR_VAL(attr, void))) |
| send_reply = true; |
| } |
| |
| /* send back reply if requested */ |
| if (send_reply) { |
| (void)skb_put(dd->outgoing_skb, |
| NL_SIZE(dd->outgoing_skb->data)); |
| if (NL_SEQ(skb->data) == 0) |
| ret = genlmsg_unicast(&init_net, |
| dd->outgoing_skb, |
| NETLINK_CB(skb).portid); |
| else |
| ret = genlmsg_multicast(&dd->nl_family, |
| dd->outgoing_skb, 0, |
| MC_FUSION, GFP_KERNEL); |
| if (ret < 0) |
| ERROR("could not reply to fusion (%d)", ret); |
| |
| /* allocate new outgoing skb */ |
| ret2 = nl_msg_new(dd, MC_FUSION); |
| if (ret2 < 0) |
| ERROR("could not allocate outgoing skb (%d)", ret2); |
| } |
| |
| /* free incoming message */ |
| kfree_skb(skb); |
| return ret; |
| } |
| |
| static int |
| nl_callback_driver(struct sk_buff *skb, struct genl_info *info) |
| { |
| struct dev_data *dd; |
| struct sk_buff *skb2; |
| unsigned long flags; |
| |
| /* locate device structure */ |
| spin_lock_irqsave(&dev_lock, flags); |
| list_for_each_entry(dd, &dev_list, dev_list) |
| if (dd->nl_family.id == NL_TYPE(skb->data)) |
| break; |
| spin_unlock_irqrestore(&dev_lock, flags); |
| if (&dd->dev_list == &dev_list) |
| return -ENODEV; |
| if (!dd->nl_enabled) |
| return -EAGAIN; |
| |
| /* queue incoming skb and wake up processing thread */ |
| skb2 = skb_clone(skb, GFP_ATOMIC); |
| if (skb2 == NULL) { |
| ERROR("failed to clone incoming skb"); |
| return -ENOMEM; |
| } else { |
| skb_queue_tail(&dd->incoming_skb_queue, skb2); |
| wake_up_process(dd->thread); |
| return 0; |
| } |
| } |
| |
| static int |
| nl_callback_fusion(struct sk_buff *skb, struct genl_info *info) |
| { |
| struct dev_data *dd; |
| unsigned long flags; |
| |
| /* locate device structure */ |
| spin_lock_irqsave(&dev_lock, flags); |
| list_for_each_entry(dd, &dev_list, dev_list) |
| if (dd->nl_family.id == NL_TYPE(skb->data)) |
| break; |
| spin_unlock_irqrestore(&dev_lock, flags); |
| if (&dd->dev_list == &dev_list) |
| return -ENODEV; |
| if (!dd->nl_enabled) |
| return -EAGAIN; |
| |
| (void)genlmsg_multicast(&dd->nl_family, |
| skb_clone(skb, GFP_ATOMIC), 0, |
| MC_FUSION, GFP_ATOMIC); |
| return 0; |
| } |
| |
| /****************************************************************************\ |
| * Interrupt processing * |
| \****************************************************************************/ |
| |
| static unsigned long interrupt_count; |
| static irqreturn_t irq_handler(int irq, void *context) |
| { |
| struct dev_data *dd = context; |
| |
| DBG("%s: interrupt #%lu", __func__, interrupt_count++); |
| |
| wake_up_process(dd->thread); |
| return IRQ_HANDLED; |
| } |
| |
| static void service_irq_legacy_acceleration(struct dev_data *dd) |
| { |
| struct fu_async_data *async_data; |
| u16 len, rx_len = 0, offset = 0; |
| u16 buf[255], rx_limit = 250 * sizeof(u16); |
| int ret = 0, counter = 0; |
| |
| async_data = nl_alloc_attr(dd->outgoing_skb->data, FU_ASYNC_DATA, |
| sizeof(*async_data) + dd->irq_param[4] + |
| 2 * sizeof(u16)); |
| if (async_data == NULL) { |
| ERROR("can't add data to async IRQ buffer"); |
| return; |
| } |
| async_data->length = dd->irq_param[4] + 2 * sizeof(u16); |
| len = async_data->length; |
| async_data->address = 0; |
| |
| while (len > 0) { |
| rx_len = (len > rx_limit) ? rx_limit : len; |
| ret = spi_read_123(dd, 0x0000, (u8 *)&buf, |
| rx_len + 4 * sizeof(u16), false); |
| if (ret < 0) |
| break; |
| |
| if (buf[3] == 0xBABE) { |
| dd->legacy_acceleration = false; |
| dd->service_irq = service_irq; |
| nl_msg_init(dd->outgoing_skb->data, dd->nl_family.id, |
| dd->nl_seq - 1, MC_FUSION); |
| return; |
| } |
| |
| if (rx_limit == rx_len) |
| usleep_range(200, 300); |
| |
| if (buf[0] == 0x6060) { |
| ERROR("data not ready"); |
| start_legacy_acceleration_canned(dd); |
| ret = -EBUSY; |
| break; |
| } else if (buf[0] == 0x8070) { |
| if (buf[1] == dd->irq_param[1] || |
| buf[1] == dd->irq_param[2]) |
| async_data->address = buf[1]; |
| |
| if (async_data->address + |
| offset / sizeof(u16) != buf[1]) { |
| ERROR("sequence number incorrect %04X", buf[1]); |
| start_legacy_acceleration_canned(dd); |
| ret = -EBUSY; |
| break; |
| } |
| } |
| counter++; |
| memcpy(async_data->data + offset, buf + 4, rx_len); |
| offset += rx_len; |
| len -= rx_len; |
| } |
| async_data->status = *(buf + rx_len / sizeof(u16) + 2); |
| |
| if (ret < 0) { |
| ERROR("can't read IRQ buffer (%d)", ret); |
| nl_msg_init(dd->outgoing_skb->data, dd->nl_family.id, |
| dd->nl_seq - 1, MC_FUSION); |
| } else { |
| (void)skb_put(dd->outgoing_skb, |
| NL_SIZE(dd->outgoing_skb->data)); |
| ret = genlmsg_multicast(&dd->nl_family, dd->outgoing_skb, 0, |
| MC_FUSION, GFP_KERNEL); |
| if (ret < 0) { |
| ERROR("can't send IRQ buffer %d", ret); |
| msleep(300); |
| if (++dd->send_fail_count >= 10 && |
| dd->fusion_process != (pid_t)0) { |
| (void)kill_pid( |
| find_get_pid(dd->fusion_process), |
| SIGKILL, 1); |
| wake_up_process(dd->thread); |
| } |
| } else { |
| dd->send_fail_count = 0; |
| } |
| ret = nl_msg_new(dd, MC_FUSION); |
| if (ret < 0) |
| ERROR("could not allocate outgoing skb (%d)", ret); |
| } |
| } |
| |
| static void service_irq(struct dev_data *dd) |
| { |
| struct fu_async_data *async_data; |
| u16 status, test, xbuf, value; |
| u16 address[2] = { 0 }; |
| u16 clear[2] = { 0 }; |
| bool read_buf[2] = {true, false}; |
| int ret, ret2; |
| |
| DBG("%s", __func__); |
| ret = dd->chip.read(dd, dd->irq_param[0], (u8 *)&status, |
| sizeof(status)); |
| if (ret < 0) { |
| ERROR("can't read IRQ status (%d)", ret); |
| return; |
| } |
| DBG("%s: status = 0x%04X", __func__, status); |
| |
| if (dd->gesture_reset) { |
| read_buf[0] = false; |
| clear[0] = dd->irq_param[10]; |
| status = dd->irq_param[10]; |
| dd->gesture_reset = false; |
| } else |
| if (status & dd->irq_param[10]) { |
| read_buf[0] = false; |
| clear[0] = dd->irq_param[10]; |
| } else if (status & dd->irq_param[18]) { |
| (void)dd->chip.read(dd, dd->irq_param[22], (u8 *)&value, |
| sizeof(value)); |
| if (value != dd->irq_param[23]) { |
| ERROR("unrecognized value [%#x] => %#x", |
| dd->irq_param[22], value); |
| clear[0] = dd->irq_param[18]; |
| ret2 = dd->chip.write(dd, dd->irq_param[0], |
| (u8 *)clear, sizeof(clear)); |
| return; |
| } |
| |
| test = status & (dd->irq_param[6] | dd->irq_param[7]); |
| |
| if (test == 0) |
| return; |
| else if (test == (dd->irq_param[6] | dd->irq_param[7])) |
| xbuf = ((status & dd->irq_param[5]) == 0) ? 0 : 1; |
| else if (test == dd->irq_param[6]) |
| xbuf = 0; |
| else if (test == dd->irq_param[7]) |
| xbuf = 1; |
| else { |
| ERROR("unexpected IRQ handler case 0x%04X ", status); |
| return; |
| } |
| |
| read_buf[1] = true; |
| address[1] = xbuf ? dd->irq_param[2] : dd->irq_param[1]; |
| address[0] = dd->irq_param[3]; |
| clear[0] = xbuf ? dd->irq_param[7] : dd->irq_param[6]; |
| clear[0] |= dd->irq_param[8]; |
| clear[0] |= dd->irq_param[18]; |
| |
| value = dd->irq_param[17]; |
| (void)dd->chip.write(dd, dd->irq_param[16], (u8 *)&value, |
| sizeof(value)); |
| } else if (status & dd->irq_param[9]) { |
| test = status & (dd->irq_param[6] | dd->irq_param[7]); |
| |
| if (test == (dd->irq_param[6] | dd->irq_param[7])) |
| xbuf = ((status & dd->irq_param[5]) != 0) ? 0 : 1; |
| else if (test == dd->irq_param[6]) |
| xbuf = 0; |
| else if (test == dd->irq_param[7]) |
| xbuf = 1; |
| else { |
| ERROR("unexpected IRQ handler case"); |
| return; |
| } |
| read_buf[1] = true; |
| address[1] = xbuf ? dd->irq_param[2] : dd->irq_param[1]; |
| |
| address[0] = dd->irq_param[3]; |
| clear[0] = dd->irq_param[6] | dd->irq_param[7] | |
| dd->irq_param[8] | dd->irq_param[9]; |
| value = dd->irq_param[13]; |
| (void)dd->chip.write(dd, dd->irq_param[12], (u8 *)&value, |
| sizeof(value)); |
| } else { |
| test = status & (dd->irq_param[6] | dd->irq_param[7]); |
| |
| if (test == 0) |
| return; |
| else if (test == (dd->irq_param[6] | dd->irq_param[7])) |
| xbuf = ((status & dd->irq_param[5]) == 0) ? 0 : 1; |
| else if (test == dd->irq_param[6]) |
| xbuf = 0; |
| else if (test == dd->irq_param[7]) |
| xbuf = 1; |
| else { |
| ERROR("unexpected IRQ handler case 0x%04X ",status); |
| return; |
| } |
| |
| address[0] = xbuf ? dd->irq_param[2] : dd->irq_param[1]; |
| clear[0] = xbuf ? dd->irq_param[7] : dd->irq_param[6]; |
| clear[0] |= dd->irq_param[8]; |
| } |
| |
| async_data = nl_alloc_attr(dd->outgoing_skb->data, FU_ASYNC_DATA, |
| sizeof(*async_data) + dd->irq_param[4]); |
| if (async_data == NULL) { |
| ERROR("can't add data to async IRQ buffer 1"); |
| return; |
| } |
| |
| async_data->status = status; |
| if (read_buf[0]) { |
| async_data->address = address[0]; |
| async_data->length = dd->irq_param[4]; |
| ret = dd->chip.read(dd, address[0], async_data->data, |
| dd->irq_param[4]); |
| } |
| |
| if (read_buf[1] && ret == 0) { |
| async_data = nl_alloc_attr(dd->outgoing_skb->data, |
| FU_ASYNC_DATA, |
| sizeof(*async_data) + |
| dd->irq_param[4]); |
| if (async_data == NULL) { |
| ERROR("can't add data to async IRQ buffer 2"); |
| nl_msg_init(dd->outgoing_skb->data, dd->nl_family.id, |
| dd->nl_seq - 1, MC_FUSION); |
| return; |
| } |
| async_data->address = address[1]; |
| async_data->length = dd->irq_param[4]; |
| async_data->status = status; |
| ret = dd->chip.read(dd, address[1], async_data->data, |
| dd->irq_param[4]); |
| } |
| |
| ret2 = dd->chip.write(dd, dd->irq_param[0], (u8 *)clear, |
| sizeof(clear)); |
| if (ret2 < 0) |
| ERROR("can't clear IRQ status (%d)", ret2); |
| |
| if (ret < 0) { |
| ERROR("can't read IRQ buffer (%d)", ret); |
| nl_msg_init(dd->outgoing_skb->data, dd->nl_family.id, |
| dd->nl_seq - 1, MC_FUSION); |
| } else { |
| DBG("%s: sending buffer", __func__); |
| (void)skb_put(dd->outgoing_skb, |
| NL_SIZE(dd->outgoing_skb->data)); |
| ret = genlmsg_multicast(&dd->nl_family, dd->outgoing_skb, 0, |
| MC_FUSION, GFP_KERNEL); |
| if (ret < 0) { |
| ERROR("can't send IRQ buffer %d", ret); |
| msleep(300); |
| if (read_buf[0] == false || |
| (++dd->send_fail_count >= 10 && |
| dd->fusion_process != (pid_t)0)) { |
| (void)kill_pid( |
| find_get_pid(dd->fusion_process), |
| SIGKILL, 1); |
| wake_up_process(dd->thread); |
| } |
| } else { |
| dd->send_fail_count = 0; |
| } |
| ret = nl_msg_new(dd, MC_FUSION); |
| if (ret < 0) |
| ERROR("could not allocate outgoing skb (%d)", ret); |
| } |
| } |
| |
| /****************************************************************************\ |
| * Processing thread * |
| \****************************************************************************/ |
| |
| static int processing_thread(void *arg) |
| { |
| struct dev_data *dd = arg; |
| struct maxim_sti_pdata *pdata = dd->spi->dev.platform_data; |
| struct sk_buff *skb; |
| char *argv[] = { pdata->touch_fusion, "daemon", |
| pdata->nl_family, |
| dd->config_file, NULL }; |
| int ret = 0, ret2 = 0; |
| bool fusion_dead; |
| |
| DRV_PT_DBG("%s started", __FUNCTION__); |
| sched_setscheduler(current, SCHED_FIFO, &dd->thread_sched); |
| |
| while (!kthread_should_stop()) { |
| set_current_state(TASK_INTERRUPTIBLE); |
| DRV_PT_DBG("Begin"); |
| |
| /* ensure that we have outgoing skb */ |
| if (dd->outgoing_skb == NULL) |
| if (nl_msg_new(dd, MC_FUSION) < 0) { |
| schedule(); |
| continue; |
| } |
| |
| /* priority 1: start up fusion process */ |
| if (dd->fusion_process != (pid_t)0 && get_pid_task( |
| find_get_pid(dd->fusion_process), |
| PIDTYPE_PID) == NULL) { |
| DRV_PT_DBG("Re-Starting TF"); |
| stop_scan_canned(dd); |
| dd->start_fusion = true; |
| dd->fusion_process = (pid_t)0; |
| dd->input_no_deconfig = true; |
| } |
| if (dd->start_fusion) { |
| DRV_PT_DBG("Spawning TF"); |
| do { |
| ret = call_usermodehelper(argv[0], argv, NULL, |
| UMH_WAIT_EXEC); |
| if (ret != 0) |
| msleep(100); |
| } while (ret != 0 && !kthread_should_stop()); |
| |
| if (ret == 0) { |
| /* power-up and reset-high */ |
| ret = regulator_control(dd, true); |
| if (ret < 0) |
| ERROR("failed to enable regulators"); |
| |
| usleep_range(300, 400); |
| pdata->reset(pdata, 1); |
| |
| dd->start_fusion = false; |
| dd->tf_status |= TF_STATUS_BUSY; |
| } |
| } |
| if (kthread_should_stop()) |
| break; |
| |
| /* priority 2: process pending Netlink messages */ |
| while ((skb = skb_dequeue(&dd->incoming_skb_queue)) != NULL) { |
| if (kthread_should_stop()) |
| break; |
| if (nl_process_msg(dd, skb) < 0) |
| skb_queue_purge(&dd->incoming_skb_queue); |
| } |
| if (kthread_should_stop()) |
| break; |
| |
| /* priority 3: suspend/resume */ |
| if (dd->suspend_in_progress && !(dd->tf_status & TF_STATUS_BUSY)) { |
| if (dd->irq_registered) { |
| disable_irq(dd->spi->irq); |
| if (dd->gesture_en) { |
| enable_gesture(dd); |
| enable_irq(dd->spi->irq); |
| } else |
| if (!dd->idle) |
| stop_scan_canned(dd); |
| else |
| stop_idle_scan(dd); |
| } |
| |
| dd->suspended = true; |
| complete(&dd->suspend_resume); |
| dd->expect_resume_ack = true; |
| dd->gesture_reset = false; |
| while (!dd->resume_in_progress) { |
| /* the line below is a MUST */ |
| set_current_state(TASK_INTERRUPTIBLE); |
| if (kthread_should_stop()) |
| break; |
| if (dd->gesture_en && dd->irq_registered && |
| pdata->irq(pdata) == 0) |
| gesture_wake_detect(dd); |
| schedule(); |
| } |
| if (dd->irq_registered) { |
| if (dd->gesture_en) |
| finish_gesture(dd); |
| else |
| if (!dd->idle) |
| start_scan_canned(dd); |
| } |
| dd->suspended = false; |
| dd->resume_in_progress = false; |
| dd->suspend_in_progress = false; |
| complete(&dd->suspend_resume); |
| |
| fusion_dead = false; |
| dd->send_fail_count = 0; |
| do { |
| if (dd->fusion_process != (pid_t)0 && |
| get_pid_task(find_get_pid( |
| dd->fusion_process), |
| PIDTYPE_PID) == NULL) { |
| fusion_dead = true; |
| break; |
| } |
| ret = nl_add_attr(dd->outgoing_skb->data, |
| FU_RESUME, NULL, 0); |
| if (ret < 0) { |
| ERROR("can't add data to resume " \ |
| "buffer"); |
| nl_msg_init(dd->outgoing_skb->data, |
| dd->nl_family.id, |
| dd->nl_seq - 1, MC_FUSION); |
| msleep(100); |
| continue; |
| } |
| (void)skb_put(dd->outgoing_skb, |
| NL_SIZE(dd->outgoing_skb->data)); |
| ret = genlmsg_multicast(&dd->nl_family, |
| dd->outgoing_skb, 0, MC_FUSION, |
| GFP_KERNEL); |
| if (ret < 0) { |
| ERROR("can't send resume message %d", |
| ret); |
| msleep(100); |
| if (++dd->send_fail_count >= 10) { |
| fusion_dead = true; |
| ret = 0; |
| } |
| } |
| ret2 = nl_msg_new(dd, MC_FUSION); |
| if (ret2 < 0) |
| ERROR("could not allocate outgoing " \ |
| "skb (%d)", ret2); |
| } while (ret != 0 && !kthread_should_stop()); |
| |
| if (!ret && dd->send_fail_count>= 10 && |
| dd->fusion_process != (pid_t)0) |
| (void)kill_pid(find_get_pid(dd->fusion_process), |
| SIGKILL, 1); |
| |
| dd->send_fail_count = 0; |
| if (fusion_dead) |
| continue; |
| } |
| if (kthread_should_stop()) |
| break; |
| |
| /* priority 4: update /sys/device information */ |
| if (dd->sysfs_update_type != DR_SYSFS_UPDATE_NONE) |
| if (!dd->expect_resume_ack) { |
| if (send_sysfs_info(dd) < 0) |
| ERROR( |
| "Can not send sysfs update to touch fusion" |
| ); |
| dd->sysfs_update_type = DR_SYSFS_UPDATE_NONE; |
| } |
| |
| /* priority 5: service interrupt */ |
| if (dd->irq_registered && !dd->expect_resume_ack && |
| ((pdata->irq(pdata) == 0) |
| || dd->gesture_reset |
| )) |
| dd->service_irq(dd); |
| if (dd->irq_registered && !dd->expect_resume_ack && |
| pdata->irq(pdata) == 0) |
| continue; |
| |
| DRV_PT_DBG("End"); |
| /* nothing more to do; sleep */ |
| schedule(); |
| } |
| |
| return 0; |
| } |
| |
| /****************************************************************************\ |
| * SYSFS interface * |
| \****************************************************************************/ |
| |
| static ssize_t chip_id_show(struct device *dev, |
| struct device_attribute *attr, char *buf) |
| { |
| struct spi_device *spi = to_spi_device(dev); |
| struct dev_data *dd = spi_get_drvdata(spi); |
| return snprintf(buf, PAGE_SIZE, "0x%04X\n", dd->chip_id); |
| } |
| |
| static ssize_t fw_ver_show(struct device *dev, |
| struct device_attribute *attr, char *buf) |
| { |
| |
| struct spi_device *spi = to_spi_device(dev); |
| struct dev_data *dd = spi_get_drvdata(spi); |
| return snprintf(buf, PAGE_SIZE, "0x%04X\n", dd->fw_ver); |
| } |
| |
| static ssize_t driver_ver_show(struct device *dev, |
| struct device_attribute *attr, char *buf) |
| { |
| |
| struct spi_device *spi = to_spi_device(dev); |
| struct dev_data *dd = spi_get_drvdata(spi); |
| return snprintf(buf, PAGE_SIZE, "0x%04X\n", dd->drv_ver); |
| } |
| |
| static ssize_t tf_ver_show(struct device *dev, |
| struct device_attribute *attr, char *buf) |
| { |
| struct spi_device *spi = to_spi_device(dev); |
| struct dev_data *dd = spi_get_drvdata(spi); |
| return snprintf(buf, PAGE_SIZE, "0x%04X\n", dd->tf_ver); |
| } |
| |
| static ssize_t panel_id_show(struct device *dev, |
| struct device_attribute *attr, char *buf) |
| { |
| return snprintf(buf, PAGE_SIZE, "0x%04X\n", panel_id); |
| } |
| |
| static ssize_t glove_show(struct device *dev, |
| struct device_attribute *attr, char *buf) |
| { |
| struct spi_device *spi = to_spi_device(dev); |
| struct dev_data *dd = spi_get_drvdata(spi); |
| return snprintf(buf, PAGE_SIZE, "%u\n", dd->glove_enabled); |
| } |
| |
| static ssize_t glove_store(struct device *dev, |
| struct device_attribute *attr, const char *buf, size_t count) |
| { |
| struct spi_device *spi = to_spi_device(dev); |
| struct dev_data *dd = spi_get_drvdata(spi); |
| u32 temp; |
| unsigned long time_left; |
| |
| mutex_lock(&dd->sysfs_update_mutex); |
| |
| if (sscanf(buf, "%u", &temp) != 1) { |
| ERROR("Invalid (%s)", buf); |
| return -EINVAL; |
| } |
| dd->glove_enabled = !!temp; |
| set_bit(DR_SYSFS_UPDATE_BIT_GLOVE, &(dd->sysfs_update_type)); |
| wake_up_process(dd->thread); |
| time_left = wait_for_completion_timeout(&dd->sysfs_ack_glove, 3*HZ); |
| |
| mutex_unlock(&dd->sysfs_update_mutex); |
| |
| if (time_left > 0) |
| return strnlen(buf, PAGE_SIZE); |
| else |
| return -EAGAIN; |
| } |
| |
| static ssize_t gesture_wakeup_show(struct device *dev, |
| struct device_attribute *attr, char *buf) |
| { |
| struct spi_device *spi = to_spi_device(dev); |
| struct dev_data *dd = spi_get_drvdata(spi); |
| return snprintf(buf, PAGE_SIZE, "%u\n", dd->gesture_en); |
| } |
| |
| static ssize_t gesture_wakeup_store(struct device *dev, |
| struct device_attribute *attr, const char *buf, size_t count) |
| { |
| struct spi_device *spi = to_spi_device(dev); |
| struct dev_data *dd = spi_get_drvdata(spi); |
| u32 temp; |
| |
| if (sscanf(buf, "%u", &temp) != 1) { |
| ERROR("Invalid (%s)", buf); |
| return -EINVAL; |
| } |
| dd->gesture_en = temp; |
| |
| return strnlen(buf, PAGE_SIZE); |
| } |
| |
| static ssize_t charger_mode_show(struct device *dev, |
| struct device_attribute *attr, char *buf) |
| { |
| struct spi_device *spi = to_spi_device(dev); |
| struct dev_data *dd = spi_get_drvdata(spi); |
| return snprintf(buf, PAGE_SIZE, "%u\n", dd->charger_mode_en); |
| } |
| |
| static ssize_t charger_mode_store(struct device *dev, |
| struct device_attribute *attr, const char *buf, size_t count) |
| { |
| struct spi_device *spi = to_spi_device(dev); |
| struct dev_data *dd = spi_get_drvdata(spi); |
| u32 temp; |
| unsigned long time_left; |
| |
| mutex_lock(&dd->sysfs_update_mutex); |
| |
| if (sscanf(buf, "%u", &temp) != 1) { |
| ERROR("Invalid (%s)", buf); |
| return -EINVAL; |
| } |
| if (temp > DR_WIRELESS_CHARGER) { |
| ERROR("Charger Mode Value Out of Range"); |
| return -EINVAL; |
| } |
| dd->charger_mode_en = temp; |
| set_bit(DR_SYSFS_UPDATE_BIT_CHARGER, &(dd->sysfs_update_type)); |
| wake_up_process(dd->thread); |
| time_left = wait_for_completion_timeout(&dd->sysfs_ack_charger, 3*HZ); |
| |
| mutex_unlock(&dd->sysfs_update_mutex); |
| |
| if (time_left > 0) |
| return strnlen(buf, PAGE_SIZE); |
| else |
| return -EAGAIN; |
| } |
| |
| static ssize_t info_show(struct device *dev, |
| struct device_attribute *attr, char *buf) |
| { |
| struct spi_device *spi = to_spi_device(dev); |
| struct dev_data *dd = spi_get_drvdata(spi); |
| return snprintf(buf, PAGE_SIZE, "chip id: 0x%04X driver ver: 0x%04X " |
| "fw ver: 0x%04X panel id: 0x%04X " |
| "tf ver: 0x%04X\n", |
| dd->chip_id, dd->drv_ver, dd->fw_ver, |
| panel_id, dd->tf_ver); |
| } |
| |
| static ssize_t screen_status_store(struct device *dev, |
| struct device_attribute *attr, const char *buf, size_t count) |
| { |
| struct spi_device *spi = to_spi_device(dev); |
| struct dev_data *dd = spi_get_drvdata(spi); |
| u32 temp; |
| |
| if (sscanf(buf, "%u", &temp) != 1) { |
| ERROR("Invalid (%s)", buf); |
| return -EINVAL; |
| } |
| if (temp) |
| resume(dev); |
| else |
| suspend(dev); |
| |
| return strnlen(buf, PAGE_SIZE); |
| } |
| |
| static ssize_t tf_status_show(struct device *dev, |
| struct device_attribute *attr, char *buf) |
| { |
| struct spi_device *spi = to_spi_device(dev); |
| struct dev_data *dd = spi_get_drvdata(spi); |
| return snprintf(buf, PAGE_SIZE, "0x%08X", dd->tf_status); |
| } |
| |
| static ssize_t default_loaded_show(struct device *dev, |
| struct device_attribute *attr, char *buf) |
| { |
| struct spi_device *spi = to_spi_device(dev); |
| struct dev_data *dd = spi_get_drvdata(spi); |
| return snprintf(buf, PAGE_SIZE, "%d", |
| (dd->tf_status & TF_STATUS_DEFAULT_LOADED)); |
| } |
| |
| static ssize_t lcd_fps_show(struct device *dev, |
| struct device_attribute *attr, char *buf) |
| { |
| struct spi_device *spi = to_spi_device(dev); |
| struct dev_data *dd = spi_get_drvdata(spi); |
| return snprintf(buf, PAGE_SIZE, "%u\n", dd->lcd_fps); |
| } |
| |
| static ssize_t lcd_fps_store(struct device *dev, |
| struct device_attribute *attr, const char *buf, size_t count) |
| { |
| struct spi_device *spi = to_spi_device(dev); |
| struct dev_data *dd = spi_get_drvdata(spi); |
| u32 temp; |
| unsigned long time_left; |
| |
| mutex_lock(&dd->sysfs_update_mutex); |
| |
| if (sscanf(buf, "%u", &temp) != 1) { |
| ERROR("Invalid (%s)", buf); |
| return -EINVAL; |
| } |
| |
| dd->lcd_fps = temp; |
| set_bit(DR_SYSFS_UPDATE_BIT_LCD_FPS, &(dd->sysfs_update_type)); |
| wake_up_process(dd->thread); |
| time_left = wait_for_completion_timeout(&dd->sysfs_ack_lcd_fps, 3*HZ); |
| |
| mutex_unlock(&dd->sysfs_update_mutex); |
| |
| if (time_left > 0) |
| return strnlen(buf, PAGE_SIZE); |
| else |
| return -EAGAIN; |
| } |
| |
| static struct device_attribute dev_attrs[] = { |
| __ATTR(fw_ver, S_IRUGO, fw_ver_show, NULL), |
| __ATTR(chip_id, S_IRUGO, chip_id_show, NULL), |
| __ATTR(tf_ver, S_IRUGO, tf_ver_show, NULL), |
| __ATTR(driver_ver, S_IRUGO, driver_ver_show, NULL), |
| __ATTR(panel_id, S_IRUGO, panel_id_show, NULL), |
| __ATTR(glove_en, S_IRUGO | S_IWUSR, glove_show, glove_store), |
| __ATTR(gesture_wakeup, S_IRUGO | S_IWUSR, gesture_wakeup_show, gesture_wakeup_store), |
| __ATTR(charger_mode, S_IRUGO | S_IWUSR, charger_mode_show, charger_mode_store), |
| __ATTR(info, S_IRUGO, info_show, NULL), |
| __ATTR(screen_status, S_IWUSR, NULL, screen_status_store), |
| __ATTR(tf_status, S_IRUGO, tf_status_show, NULL), |
| __ATTR(default_loaded, S_IRUGO, default_loaded_show, NULL), |
| __ATTR(lcd_fps, S_IRUGO | S_IWUSR, lcd_fps_show, lcd_fps_store), |
| }; |
| |
| static int create_sysfs_entries(struct dev_data *dd) |
| { |
| int i, ret = 0; |
| |
| for (i = 0; i < ARRAY_SIZE(dev_attrs); i++) { |
| ret = device_create_file(&dd->spi->dev, &dev_attrs[i]); |
| if (ret) { |
| for (; i >= 0; --i) { |
| device_remove_file(&dd->spi->dev, |
| &dev_attrs[i]); |
| dd->sysfs_created--; |
| } |
| break; |
| } |
| dd->sysfs_created++; |
| } |
| return ret; |
| } |
| |
| static void remove_sysfs_entries(struct dev_data *dd) |
| { |
| int i; |
| |
| for (i = 0; i < ARRAY_SIZE(dev_attrs); i++) |
| if (dd->sysfs_created && dd->sysfs_created--) |
| device_remove_file(&dd->spi->dev, &dev_attrs[i]); |
| } |
| |
| static int send_sysfs_info(struct dev_data *dd) |
| { |
| struct fu_sysfs_info *sysfs_info; |
| int ret, ret2; |
| |
| sysfs_info = nl_alloc_attr(dd->outgoing_skb->data, |
| FU_SYSFS_INFO, |
| sizeof(*sysfs_info)); |
| if (sysfs_info == NULL) { |
| ERROR("Can't allocate sysfs_info"); |
| return -EAGAIN; |
| } |
| sysfs_info->type = dd->sysfs_update_type; |
| |
| sysfs_info->glove_value = dd->glove_enabled; |
| sysfs_info->charger_value = dd->charger_mode_en; |
| sysfs_info->lcd_fps_value = dd->lcd_fps; |
| |
| (void)skb_put(dd->outgoing_skb, |
| NL_SIZE(dd->outgoing_skb->data)); |
| ret = genlmsg_multicast(&dd->nl_family, |
| dd->outgoing_skb, 0, |
| MC_FUSION, GFP_KERNEL); |
| if (ret < 0) |
| ERROR("could not reply to fusion (%d)", ret); |
| |
| /* allocate new outgoing skb */ |
| ret2 = nl_msg_new(dd, MC_FUSION); |
| if (ret2 < 0) |
| ERROR("could not allocate outgoing skb (%d)", ret2); |
| |
| return 0; |
| } |
| |
| /****************************************************************************\ |
| * Driver initialization * |
| \****************************************************************************/ |
| |
| static int probe(struct spi_device *spi) |
| { |
| struct maxim_sti_pdata *pdata = spi->dev.platform_data; |
| struct dev_data *dd; |
| unsigned long flags; |
| int ret, i; |
| void *ptr; |
| |
| #ifdef CONFIG_OF |
| if (pdata == NULL && spi->dev.of_node) { |
| pdata = devm_kzalloc(&spi->dev, |
| sizeof(struct maxim_sti_pdata), GFP_KERNEL); |
| if (!pdata) { |
| dev_err(&spi->dev, "failed to allocate memory\n"); |
| return -ENOMEM; |
| } |
| |
| ret = maxim_parse_dt(&spi->dev, pdata); |
| if (ret) |
| goto of_parse_failure; |
| spi->dev.platform_data = pdata; |
| } |
| #endif |
| |
| pdata->init = maxim_sti_gpio_init; |
| pdata->reset = maxim_sti_gpio_reset; |
| pdata->irq = maxim_sti_gpio_irq; |
| |
| pdata->nl_mc_groups = DEF_NL_MC_GROUPS; |
| pdata->chip_access_method = DEF_CHIP_ACC_METHOD; |
| pdata->tx_buf_size = BUF_SIZE; |
| pdata->rx_buf_size = BUF_SIZE; |
| pdata->nl_family = TOUCH_FUSION; |
| |
| dev_dbg(&spi->dev, "maxim_sti,reset-gpio: %d", pdata->gpio_reset); |
| dev_dbg(&spi->dev, "maxim_sti,irq-gpio: %d", pdata->gpio_irq); |
| dev_dbg(&spi->dev, "maxim_sti,nl_mc_groups: %d", pdata->nl_mc_groups); |
| dev_dbg(&spi->dev, "maxim_sti,chip_access_method: %d", pdata->chip_access_method); |
| dev_dbg(&spi->dev, "maxim_sti,default_reset_state: %d", pdata->default_reset_state); |
| dev_dbg(&spi->dev, "maxim_sti,tx_buf_size: %d", pdata->tx_buf_size); |
| dev_dbg(&spi->dev, "maxim_sti,rx_buf_size: %d", pdata->rx_buf_size); |
| dev_dbg(&spi->dev, "maxim_sti,touch_fusion: %s", pdata->touch_fusion); |
| dev_dbg(&spi->dev, "maxim_sti,config_file: %s", pdata->config_file); |
| dev_dbg(&spi->dev, "maxim_sti,nl_family: %s", pdata->nl_family); |
| dev_dbg(&spi->dev, "maxim_sti,fw_name: %s", pdata->fw_name); |
| |
| /* validate platform data */ |
| if (pdata == NULL || pdata->init == NULL || pdata->reset == NULL || |
| pdata->irq == NULL || pdata->touch_fusion == NULL || |
| pdata->config_file == NULL || pdata->nl_family == NULL || |
| GENL_CHK(pdata->nl_family) || |
| pdata->nl_mc_groups < MC_GROUPS || |
| pdata->chip_access_method == 0 || |
| pdata->chip_access_method > ARRAY_SIZE(chip_access_methods) || |
| pdata->default_reset_state > 1 || |
| pdata->tx_buf_size == 0 || |
| pdata->rx_buf_size == 0) { |
| dev_err(&spi->dev, "invalid platform data!"); |
| ret = -EINVAL; |
| goto of_parse_failure; |
| } |
| |
| /* device context: allocate structure */ |
| dd = kzalloc(sizeof(*dd) + |
| sizeof(*dd->nl_ops) * pdata->nl_mc_groups + |
| sizeof(*dd->nl_mc_groups) * pdata->nl_mc_groups, |
| GFP_KERNEL); |
| if (dd == NULL) { |
| dev_err(&spi->dev, "cannot allocate memory!"); |
| ret = -ENOMEM; |
| goto of_parse_failure; |
| } |
| |
| /* device context: set up dynamic allocation pointers */ |
| ptr = (void *)dd + sizeof(*dd); |
| dd->nl_ops = ptr; |
| ptr += sizeof(*dd->nl_ops) * pdata->nl_mc_groups; |
| dd->nl_mc_groups = ptr; |
| |
| /* device context: initialize structure members */ |
| spi_set_drvdata(spi, dd); |
| spi->bits_per_word = 16; |
| spi_setup(spi); |
| dd->spi = spi; |
| dd->nl_seq = 1; |
| init_completion(&dd->suspend_resume); |
| |
| /* allocate DMA memory */ |
| dd->tx_buf = kzalloc(pdata->tx_buf_size + pdata->rx_buf_size, |
| GFP_KERNEL | GFP_DMA); |
| if (dd->tx_buf == NULL) { |
| dev_err(&spi->dev, "cannot allocate DMA accesible memory"); |
| ret = -ENOMEM; |
| goto buf_alloc_failure; |
| } |
| dd->rx_buf = dd->tx_buf + pdata->tx_buf_size; |
| memset(dd->tx_buf, 0xFF, pdata->tx_buf_size); |
| (void)set_chip_access_method(dd, pdata->chip_access_method); |
| |
| /* initialize regulators */ |
| regulator_init(dd); |
| |
| /* initialize platform */ |
| |
| /* Get pinctrl if target uses pinctrl */ |
| dd->ts_pinctrl = devm_pinctrl_get(&(spi->dev)); |
| if (IS_ERR_OR_NULL(dd->ts_pinctrl)) { |
| ret = PTR_ERR(dd->ts_pinctrl); |
| pr_debug("Target does not use pinctrl %d\n", ret); |
| } |
| |
| dd->pinctrl_state_active |
| = pinctrl_lookup_state(dd->ts_pinctrl, "pmx_ts_active"); |
| if (IS_ERR_OR_NULL(dd->pinctrl_state_active)) { |
| ret = PTR_ERR(dd->pinctrl_state_active); |
| pr_debug("Can not lookup active pinstate %d\n", ret); |
| } |
| |
| dd->pinctrl_state_suspended |
| = pinctrl_lookup_state(dd->ts_pinctrl, "pmx_ts_suspend"); |
| if (IS_ERR_OR_NULL(dd->pinctrl_state_suspended)) { |
| ret = PTR_ERR(dd->pinctrl_state_suspended); |
| pr_debug("Can not lookup active pinstate %d\n", ret); |
| } |
| ret = pinctrl_select_state(dd->ts_pinctrl, |
| dd->pinctrl_state_active); |
| if (ret < 0) |
| pr_debug("pinctrl set active fail\n"); |
| |
| |
| ret = pdata->init(pdata, true); |
| if (ret < 0) |
| goto pinit_failure; |
| |
| /* Netlink: initialize incoming skb queue */ |
| skb_queue_head_init(&dd->incoming_skb_queue); |
| |
| /* Netlink: register GENL family */ |
| dd->nl_family.id = GENL_ID_GENERATE; |
| dd->nl_family.version = NL_FAMILY_VERSION; |
| GENL_COPY(dd->nl_family.name, pdata->nl_family); |
| |
| /* Netlink: register family ops */ |
| for (i = 0; i < MC_GROUPS; i++) { |
| dd->nl_ops[i].cmd = i; |
| dd->nl_ops[i].doit = nl_callback_noop; |
| } |
| dd->nl_ops[MC_DRIVER].doit = nl_callback_driver; |
| dd->nl_ops[MC_FUSION].doit = nl_callback_fusion; |
| dd->nl_ops[MC_EVENT_BROADCAST].doit = nl_callback_noop; |
| |
| /* Netlink: register family multicast groups */ |
| GENL_COPY(dd->nl_mc_groups[MC_DRIVER].name, MC_DRIVER_NAME); |
| GENL_COPY(dd->nl_mc_groups[MC_FUSION].name, MC_FUSION_NAME); |
| GENL_COPY(dd->nl_mc_groups[MC_EVENT_BROADCAST].name, |
| MC_EVENT_BROADCAST_NAME); |
| ret = _genl_register_family_with_ops_grps(&dd->nl_family, |
| dd->nl_ops, |
| MC_GROUPS, |
| dd->nl_mc_groups, |
| MC_GROUPS); |
| if (ret < 0) { |
| dev_err(&spi->dev, "error registering nl_mc_group"); |
| goto nl_family_failure; |
| } |
| dd->nl_mc_group_count = MC_GROUPS; |
| |
| /* Netlink: pre-allocate outgoing skb */ |
| ret = nl_msg_new(dd, MC_FUSION); |
| if (ret < 0) { |
| dev_err(&spi->dev, "error alloc msg"); |
| goto nl_failure; |
| } |
| |
| /* start processing thread */ |
| dd->thread_sched.sched_priority = MAX_USER_RT_PRIO / 2; |
| dd->thread = kthread_run(processing_thread, dd, pdata->nl_family); |
| if (IS_ERR(dd->thread)) { |
| dev_err(&spi->dev, "error creating kthread!"); |
| ret = PTR_ERR(dd->thread); |
| goto kthread_failure; |
| } |
| |
| /* Netlink: ready to start processing incoming messages */ |
| dd->nl_enabled = true; |
| |
| /* No sysfs update initially */ |
| dd->sysfs_update_type = DR_SYSFS_UPDATE_NONE; |
| init_completion(&dd->sysfs_ack_glove); |
| init_completion(&dd->sysfs_ack_charger); |
| init_completion(&dd->sysfs_ack_lcd_fps); |
| mutex_init(&dd->sysfs_update_mutex); |
| |
| /* add us to the devices list */ |
| spin_lock_irqsave(&dev_lock, flags); |
| list_add_tail(&dd->dev_list, &dev_list); |
| spin_unlock_irqrestore(&dev_lock, flags); |
| |
| ret = create_sysfs_entries(dd); |
| if (ret) { |
| dev_err(&spi->dev, "failed to create sysfs file"); |
| goto sysfs_failure; |
| } |
| snprintf(dd->config_file, CFG_FILE_NAME_MAX, pdata->config_file, panel_id); |
| dev_dbg(&spi->dev, "configuration file: %s", dd->config_file); |
| |
| /* start up Touch Fusion */ |
| dd->start_fusion = true; |
| wake_up_process(dd->thread); |
| dev_info(&spi->dev, "%s: driver loaded; version %s; release date %s", |
| dd->nl_family.name, DRIVER_VERSION, DRIVER_RELEASE); |
| |
| return 0; |
| |
| sysfs_failure: |
| dd->nl_enabled = false; |
| spin_lock_irqsave(&dev_lock, flags); |
| list_del(&dd->dev_list); |
| spin_unlock_irqrestore(&dev_lock, flags); |
| (void)kthread_stop(dd->thread); |
| kthread_failure: |
| if (dd->outgoing_skb) |
| kfree_skb(dd->outgoing_skb); |
| nl_failure: |
| genl_unregister_family(&dd->nl_family); |
| nl_family_failure: |
| pdata->init(pdata, false); |
| pinit_failure: |
| regulator_uninit(dd); |
| kfree(dd->tx_buf); |
| buf_alloc_failure: |
| spi_set_drvdata(spi, NULL); |
| kfree(dd); |
| of_parse_failure: |
| spi->dev.platform_data = NULL; |
| devm_kfree(&spi->dev, pdata); |
| return ret; |
| } |
| |
| static int remove(struct spi_device *spi) |
| { |
| struct maxim_sti_pdata *pdata = spi->dev.platform_data; |
| struct dev_data *dd = spi_get_drvdata(spi); |
| unsigned long flags; |
| u8 i; |
| |
| dev_dbg(&spi->dev, "remove"); |
| |
| /* BEWARE: tear-down sequence below is carefully staged: */ |
| /* 1) first the feeder of Netlink messages to the processing thread */ |
| /* is turned off */ |
| /* 2) then the thread itself is shut down */ |
| /* 3) then Netlink family is torn down since no one would be using */ |
| /* it at this point */ |
| /* 4) above step (3) insures that all Netlink senders are */ |
| /* definitely gone and it is safe to free up outgoing skb buffer */ |
| /* and incoming skb queue */ |
| dev_dbg(&spi->dev, "stopping thread.."); |
| dd->nl_enabled = false; |
| (void)kthread_stop(dd->thread); |
| dev_dbg(&spi->dev, "kthread stopped."); |
| genl_unregister_family(&dd->nl_family); |
| if (dd->outgoing_skb) |
| kfree_skb(dd->outgoing_skb); |
| skb_queue_purge(&dd->incoming_skb_queue); |
| |
| if (dd->fusion_process != (pid_t)0) |
| (void)kill_pid(find_get_pid(dd->fusion_process), SIGKILL, 1); |
| |
| dev_dbg(&spi->dev, "removing sysfs entries.."); |
| if (dd->parent != NULL) |
| sysfs_remove_link(dd->input_dev[0]->dev.kobj.parent, |
| MAXIM_STI_NAME); |
| remove_sysfs_entries(dd); |
| |
| dev_dbg(&spi->dev, "unregister input devices.."); |
| for (i = 0; i < INPUT_DEVICES; i++) |
| if (dd->input_dev[i]) |
| input_unregister_device(dd->input_dev[i]); |
| #if defined(CONFIG_FB) |
| fb_unregister_client(&dd->fb_notifier); |
| #endif |
| |
| if (dd->irq_registered) { |
| dev_dbg(&spi->dev, "disabling interrupt"); |
| disable_irq(dd->spi->irq); |
| dev_dbg(&spi->dev, "stopping scan"); |
| stop_scan_canned(dd); |
| free_irq(dd->spi->irq, dd); |
| } |
| |
| spin_lock_irqsave(&dev_lock, flags); |
| list_del(&dd->dev_list); |
| spin_unlock_irqrestore(&dev_lock, flags); |
| |
| pdata->reset(pdata, 0); |
| usleep_range(100, 120); |
| regulator_control(dd, false); |
| pdata->init(pdata, false); |
| regulator_uninit(dd); |
| |
| dev_dbg(&spi->dev, "detaching from spi.."); |
| spi_set_drvdata(spi, NULL); |
| kzfree(dd); |
| spi->dev.platform_data = NULL; |
| devm_kfree(&spi->dev, pdata); |
| |
| dev_info(&spi->dev, "driver unloaded"); |
| return 0; |
| } |
| |
| static void shutdown(struct spi_device *spi) |
| { |
| struct maxim_sti_pdata *pdata = spi->dev.platform_data; |
| struct dev_data *dd = spi_get_drvdata(spi); |
| |
| if (dd->parent != NULL) |
| sysfs_remove_link(dd->input_dev[0]->dev.kobj.parent, |
| MAXIM_STI_NAME); |
| remove_sysfs_entries(dd); |
| |
| pdata->reset(pdata, 0); |
| usleep_range(100, 120); |
| regulator_control(dd, false); |
| } |
| |
| /****************************************************************************\ |
| * Module initialization * |
| \****************************************************************************/ |
| |
| static const struct spi_device_id id[] = { |
| { MAXIM_STI_NAME, 0 }, |
| { } |
| }; |
| |
| MODULE_DEVICE_TABLE(spi, id); |
| |
| #ifdef CONFIG_OF |
| static struct of_device_id maxim_match_table[] = { |
| { .compatible = "maxim,maxim_sti",}, |
| { }, |
| }; |
| #endif |
| |
| static struct spi_driver driver = { |
| .probe = probe, |
| .remove = remove, |
| .shutdown = shutdown, |
| .id_table = id, |
| .driver = { |
| .name = MAXIM_STI_NAME, |
| #ifdef CONFIG_OF |
| .of_match_table = maxim_match_table, |
| #endif |
| .owner = THIS_MODULE, |
| #ifdef CONFIG_PM_SLEEP |
| .pm = &pm_ops, |
| #endif |
| }, |
| }; |
| |
| static int maxim_sti_init(void) |
| { |
| INIT_LIST_HEAD(&dev_list); |
| spin_lock_init(&dev_lock); |
| return spi_register_driver(&driver); |
| } |
| |
| static void maxim_sti_exit(void) |
| { |
| spi_unregister_driver(&driver); |
| } |
| |
| module_param(panel_id, ushort, S_IRUGO); |
| MODULE_PARM_DESC(panel_id, "Touch Panel ID Configuration"); |
| |
| module_init(maxim_sti_init); |
| module_exit(maxim_sti_exit); |
| |
| MODULE_AUTHOR("Maxim Integrated Products, Inc."); |
| MODULE_DESCRIPTION("Maxim SmartTouch Imager Touchscreen Driver"); |
| MODULE_LICENSE("GPL v2"); |
| MODULE_VERSION(DRIVER_VERSION); |