cregit-Linux how code gets into the kernel

Release 4.11 drivers/input/touchscreen/melfas_mip4.c

/*
 * MELFAS MIP4 Touchscreen
 *
 * Copyright (C) 2016 MELFAS Inc.
 *
 * Author : Sangwon Jee <jeesw@melfas.com>
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU General Public License for more details.
 */

#include <linux/acpi.h>
#include <linux/delay.h>
#include <linux/firmware.h>
#include <linux/gpio/consumer.h>
#include <linux/i2c.h>
#include <linux/input.h>
#include <linux/input/mt.h>
#include <linux/interrupt.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/slab.h>
#include <asm/unaligned.h>


#define MIP4_DEVICE_NAME	"mip4_ts"

/*****************************************************************
 * Protocol
 * Version : MIP 4.0 Rev 5.4
 *****************************************************************/

/* Address */

#define MIP4_R0_BOOT				0x00

#define MIP4_R1_BOOT_MODE			0x01

#define MIP4_R1_BOOT_BUF_ADDR			0x10

#define MIP4_R1_BOOT_STATUS			0x20

#define MIP4_R1_BOOT_CMD			0x30

#define MIP4_R1_BOOT_TARGET_ADDR		0x40

#define MIP4_R1_BOOT_SIZE			0x44


#define MIP4_R0_INFO				0x01

#define MIP4_R1_INFO_PRODUCT_NAME		0x00

#define MIP4_R1_INFO_RESOLUTION_X		0x10

#define MIP4_R1_INFO_RESOLUTION_Y		0x12

#define MIP4_R1_INFO_NODE_NUM_X			0x14

#define MIP4_R1_INFO_NODE_NUM_Y			0x15

#define MIP4_R1_INFO_KEY_NUM			0x16

#define MIP4_R1_INFO_PRESSURE_NUM		0x17

#define MIP4_R1_INFO_LENGTH_X			0x18

#define MIP4_R1_INFO_LENGTH_Y			0x1A

#define MIP4_R1_INFO_PPM_X			0x1C

#define MIP4_R1_INFO_PPM_Y			0x1D

#define MIP4_R1_INFO_VERSION_BOOT		0x20

#define MIP4_R1_INFO_VERSION_CORE		0x22

#define MIP4_R1_INFO_VERSION_APP		0x24

#define MIP4_R1_INFO_VERSION_PARAM		0x26

#define MIP4_R1_INFO_SECT_BOOT_START		0x30

#define MIP4_R1_INFO_SECT_BOOT_END		0x31

#define MIP4_R1_INFO_SECT_CORE_START		0x32

#define MIP4_R1_INFO_SECT_CORE_END		0x33

#define MIP4_R1_INFO_SECT_APP_START		0x34

#define MIP4_R1_INFO_SECT_APP_END		0x35

#define MIP4_R1_INFO_SECT_PARAM_START		0x36

#define MIP4_R1_INFO_SECT_PARAM_END		0x37

#define MIP4_R1_INFO_BUILD_DATE			0x40

#define MIP4_R1_INFO_BUILD_TIME			0x44

#define MIP4_R1_INFO_CHECKSUM_PRECALC		0x48

#define MIP4_R1_INFO_CHECKSUM_REALTIME		0x4A

#define MIP4_R1_INFO_PROTOCOL_NAME		0x50

#define MIP4_R1_INFO_PROTOCOL_VERSION		0x58

#define MIP4_R1_INFO_IC_ID			0x70

#define MIP4_R1_INFO_IC_NAME			0x71

#define MIP4_R1_INFO_IC_VENDOR_ID		0x75

#define MIP4_R1_INFO_IC_HW_CATEGORY		0x77

#define MIP4_R1_INFO_CONTACT_THD_SCR		0x78

#define MIP4_R1_INFO_CONTACT_THD_KEY		0x7A

#define MIP4_R1_INFO_PID				0x7C

#define MIP4_R1_INFO_VID				0x7E

#define MIP4_R1_INFO_SLAVE_ADDR			0x80


#define MIP4_R0_EVENT				0x02

#define MIP4_R1_EVENT_SUPPORTED_FUNC		0x00

#define MIP4_R1_EVENT_FORMAT			0x04

#define MIP4_R1_EVENT_SIZE			0x06

#define MIP4_R1_EVENT_PACKET_INFO		0x10

#define MIP4_R1_EVENT_PACKET_DATA		0x11


#define MIP4_R0_CTRL				0x06

#define MIP4_R1_CTRL_READY_STATUS		0x00

#define MIP4_R1_CTRL_EVENT_READY		0x01

#define MIP4_R1_CTRL_MODE			0x10

#define MIP4_R1_CTRL_EVENT_TRIGGER_TYPE		0x11

#define MIP4_R1_CTRL_RECALIBRATE		0x12

#define MIP4_R1_CTRL_POWER_STATE		0x13

#define MIP4_R1_CTRL_GESTURE_TYPE		0x14

#define MIP4_R1_CTRL_DISABLE_ESD_ALERT		0x18

#define MIP4_R1_CTRL_CHARGER_MODE		0x19

#define MIP4_R1_CTRL_HIGH_SENS_MODE		0x1A

#define MIP4_R1_CTRL_WINDOW_MODE		0x1B

#define MIP4_R1_CTRL_PALM_REJECTION		0x1C

#define MIP4_R1_CTRL_EDGE_CORRECTION		0x1D

#define MIP4_R1_CTRL_ENTER_GLOVE_MODE		0x1E

#define MIP4_R1_CTRL_I2C_ON_LPM			0x1F

#define MIP4_R1_CTRL_GESTURE_DEBUG		0x20

#define MIP4_R1_CTRL_PALM_EVENT			0x22

#define MIP4_R1_CTRL_PROXIMITY_SENSING		0x23

/* Value */

#define MIP4_BOOT_MODE_BOOT			0x01

#define MIP4_BOOT_MODE_APP			0x02


#define MIP4_BOOT_STATUS_BUSY			0x05

#define MIP4_BOOT_STATUS_ERROR			0x0E

#define MIP4_BOOT_STATUS_DONE			0xA0


#define MIP4_BOOT_CMD_MASS_ERASE		0x15

#define MIP4_BOOT_CMD_PROGRAM			0x54

#define MIP4_BOOT_CMD_ERASE			0x8F

#define MIP4_BOOT_CMD_WRITE			0xA5

#define MIP4_BOOT_CMD_READ			0xC2


#define MIP4_EVENT_INPUT_TYPE_KEY		0

#define MIP4_EVENT_INPUT_TYPE_SCREEN		1

#define MIP4_EVENT_INPUT_TYPE_PROXIMITY		2


#define I2C_RETRY_COUNT				3	
/* 2~ */


#define MIP4_BUF_SIZE				128

#define MIP4_MAX_FINGERS			10

#define MIP4_MAX_KEYS				4


#define MIP4_TOUCH_MAJOR_MIN			0

#define MIP4_TOUCH_MAJOR_MAX			255

#define MIP4_TOUCH_MINOR_MIN			0

#define MIP4_TOUCH_MINOR_MAX			255

#define MIP4_PRESSURE_MIN			0

#define MIP4_PRESSURE_MAX			255


#define MIP4_FW_NAME			"melfas_mip4.fw"

#define MIP4_FW_UPDATE_DEBUG		0	
/* 0 (default) or 1 */


struct mip4_fw_version {
	
u16 boot;
	
u16 core;
	
u16 app;
	
u16 param;
};


struct mip4_ts {
	
struct i2c_client *client;
	
struct input_dev *input;
	
struct gpio_desc *gpio_ce;

	
char phys[32];
	
char product_name[16];
	
u16 product_id;
	
char ic_name[4];
	
char fw_name[32];

	
unsigned int max_x;
	
unsigned int max_y;
	
u8 node_x;
	
u8 node_y;
	
u8 node_key;
	
unsigned int ppm_x;
	
unsigned int ppm_y;

	
struct mip4_fw_version fw_version;

	
unsigned int event_size;
	
unsigned int event_format;

	
unsigned int key_num;
	
unsigned short key_code[MIP4_MAX_KEYS];

	
bool wake_irq_enabled;

	
u8 buf[MIP4_BUF_SIZE];
};


static int mip4_i2c_xfer(struct mip4_ts *ts, char *write_buf, unsigned int write_len, char *read_buf, unsigned int read_len) { struct i2c_msg msg[] = { { .addr = ts->client->addr, .flags = 0, .buf = write_buf, .len = write_len, }, { .addr = ts->client->addr, .flags = I2C_M_RD, .buf = read_buf, .len = read_len, }, }; int retry = I2C_RETRY_COUNT; int res; int error; do { res = i2c_transfer(ts->client->adapter, msg, ARRAY_SIZE(msg)); if (res == ARRAY_SIZE(msg)) return 0; error = res < 0 ? res : -EIO; dev_err(&ts->client->dev, "%s - i2c_transfer failed: %d (%d)\n", __func__, error, res); } while (--retry); return error; }

Contributors

PersonTokensPropCommitsCommitProp
Sangwon Jee168100.00%1100.00%
Total168100.00%1100.00%


static void mip4_parse_fw_version(const u8 *buf, struct mip4_fw_version *v) { v->boot = get_unaligned_le16(buf + 0); v->core = get_unaligned_le16(buf + 2); v->app = get_unaligned_le16(buf + 4); v->param = get_unaligned_le16(buf + 6); }

Contributors

PersonTokensPropCommitsCommitProp
Sangwon Jee60100.00%1100.00%
Total60100.00%1100.00%

/* * Read chip firmware version */
static int mip4_get_fw_version(struct mip4_ts *ts) { u8 cmd[] = { MIP4_R0_INFO, MIP4_R1_INFO_VERSION_BOOT }; u8 buf[sizeof(ts->fw_version)]; int error; error = mip4_i2c_xfer(ts, cmd, sizeof(cmd), buf, sizeof(buf)); if (error) { memset(&ts->fw_version, 0xff, sizeof(ts->fw_version)); return error; } mip4_parse_fw_version(buf, &ts->fw_version); return 0; }

Contributors

PersonTokensPropCommitsCommitProp
Sangwon Jee95100.00%1100.00%
Total95100.00%1100.00%

/* * Fetch device characteristics */
static int mip4_query_device(struct mip4_ts *ts) { int error; u8 cmd[2]; u8 buf[14]; /* Product name */ cmd[0] = MIP4_R0_INFO; cmd[1] = MIP4_R1_INFO_PRODUCT_NAME; error = mip4_i2c_xfer(ts, cmd, sizeof(cmd), ts->product_name, sizeof(ts->product_name)); if (error) dev_warn(&ts->client->dev, "Failed to retrieve product name: %d\n", error); else dev_dbg(&ts->client->dev, "product name: %.*s\n", (int)sizeof(ts->product_name), ts->product_name); /* Product ID */ cmd[0] = MIP4_R0_INFO; cmd[1] = MIP4_R1_INFO_PID; error = mip4_i2c_xfer(ts, cmd, sizeof(cmd), buf, 2); if (error) { dev_warn(&ts->client->dev, "Failed to retrieve product id: %d\n", error); } else { ts->product_id = get_unaligned_le16(&buf[0]); dev_dbg(&ts->client->dev, "product id: %04X\n", ts->product_id); } /* Firmware name */ snprintf(ts->fw_name, sizeof(ts->fw_name), "melfas_mip4_%04X.fw", ts->product_id); dev_dbg(&ts->client->dev, "firmware name: %s\n", ts->fw_name); /* IC name */ cmd[0] = MIP4_R0_INFO; cmd[1] = MIP4_R1_INFO_IC_NAME; error = mip4_i2c_xfer(ts, cmd, sizeof(cmd), ts->ic_name, sizeof(ts->ic_name)); if (error) dev_warn(&ts->client->dev, "Failed to retrieve IC name: %d\n", error); else dev_dbg(&ts->client->dev, "IC name: %.*s\n", (int)sizeof(ts->ic_name), ts->ic_name); /* Firmware version */ error = mip4_get_fw_version(ts); if (error) dev_warn(&ts->client->dev, "Failed to retrieve FW version: %d\n", error); else dev_dbg(&ts->client->dev, "F/W Version: %04X %04X %04X %04X\n", ts->fw_version.boot, ts->fw_version.core, ts->fw_version.app, ts->fw_version.param); /* Resolution */ cmd[0] = MIP4_R0_INFO; cmd[1] = MIP4_R1_INFO_RESOLUTION_X; error = mip4_i2c_xfer(ts, cmd, sizeof(cmd), buf, 14); if (error) { dev_warn(&ts->client->dev, "Failed to retrieve touchscreen parameters: %d\n", error); } else { ts->max_x = get_unaligned_le16(&buf[0]); ts->max_y = get_unaligned_le16(&buf[2]); dev_dbg(&ts->client->dev, "max_x: %d, max_y: %d\n", ts->max_x, ts->max_y); ts->node_x = buf[4]; ts->node_y = buf[5]; ts->node_key = buf[6]; dev_dbg(&ts->client->dev, "node_x: %d, node_y: %d, node_key: %d\n", ts->node_x, ts->node_y, ts->node_key); ts->ppm_x = buf[12]; ts->ppm_y = buf[13]; dev_dbg(&ts->client->dev, "ppm_x: %d, ppm_y: %d\n", ts->ppm_x, ts->ppm_y); /* Key ts */ if (ts->node_key > 0) ts->key_num = ts->node_key; } /* Protocol */ cmd[0] = MIP4_R0_EVENT; cmd[1] = MIP4_R1_EVENT_SUPPORTED_FUNC; error = mip4_i2c_xfer(ts, cmd, sizeof(cmd), buf, 7); if (error) { dev_warn(&ts->client->dev, "Failed to retrieve device type: %d\n", error); ts->event_format = 0xff; } else { ts->event_format = get_unaligned_le16(&buf[4]); ts->event_size = buf[6]; dev_dbg(&ts->client->dev, "event_format: %d, event_size: %d\n", ts->event_format, ts->event_size); if (ts->event_format == 2 || ts->event_format > 3) dev_warn(&ts->client->dev, "Unknown event format %d\n", ts->event_format); } return 0; }

Contributors

PersonTokensPropCommitsCommitProp
Sangwon Jee726100.00%5100.00%
Total726100.00%5100.00%


static int mip4_power_on(struct mip4_ts *ts) { if (ts->gpio_ce) { gpiod_set_value_cansleep(ts->gpio_ce, 1); /* Booting delay : 200~300ms */ usleep_range(200 * 1000, 300 * 1000); } return 0; }

Contributors

PersonTokensPropCommitsCommitProp
Sangwon Jee43100.00%1100.00%
Total43100.00%1100.00%


static void mip4_power_off(struct mip4_ts *ts) { if (ts->gpio_ce) gpiod_set_value_cansleep(ts->gpio_ce, 0); }

Contributors

PersonTokensPropCommitsCommitProp
Sangwon Jee26100.00%1100.00%
Total26100.00%1100.00%

/* * Clear touch input event status */
static void mip4_clear_input(struct mip4_ts *ts) { int i; /* Screen */ for (i = 0; i < MIP4_MAX_FINGERS; i++) { input_mt_slot(ts->input, i); input_mt_report_slot_state(ts->input, MT_TOOL_FINGER, 0); } /* Keys */ for (i = 0; i < ts->key_num; i++) input_report_key(ts->input, ts->key_code[i], 0); input_sync(ts->input); }

Contributors

PersonTokensPropCommitsCommitProp
Sangwon Jee89100.00%1100.00%
Total89100.00%1100.00%


static int mip4_enable(struct mip4_ts *ts) { int error; error = mip4_power_on(ts); if (error) return error; enable_irq(ts->client->irq); return 0; }

Contributors

PersonTokensPropCommitsCommitProp
Sangwon Jee40100.00%1100.00%
Total40100.00%1100.00%


static void mip4_disable(struct mip4_ts *ts) { disable_irq(ts->client->irq); mip4_power_off(ts); mip4_clear_input(ts); }

Contributors

PersonTokensPropCommitsCommitProp
Sangwon Jee30100.00%1100.00%
Total30100.00%1100.00%

/***************************************************************** * Input handling *****************************************************************/
static void mip4_report_keys(struct mip4_ts *ts, u8 *packet) { u8 key; bool down; switch (ts->event_format) { case 0: case 1: key = packet[0] & 0x0F; down = packet[0] & 0x80; break; case 3: default: key = packet[0] & 0x0F; down = packet[1] & 0x01; break; } /* Report key event */ if (key >= 1 && key <= ts->key_num) { unsigned short keycode = ts->key_code[key - 1]; dev_dbg(&ts->client->dev, "Key - ID: %d, keycode: %d, state: %d\n", key, keycode, down); input_event(ts->input, EV_MSC, MSC_SCAN, keycode); input_report_key(ts->input, keycode, down); } else { dev_err(&ts->client->dev, "Unknown key: %d\n", key); } }

Contributors

PersonTokensPropCommitsCommitProp
Sangwon Jee164100.00%1100.00%
Total164100.00%1100.00%


static void mip4_report_touch(struct mip4_ts *ts, u8 *packet) { int id; bool hover; bool palm; bool state; u16 x, y; u8 pressure_stage = 0; u8 pressure; u8 size; u8 touch_major; u8 touch_minor; switch (ts->event_format) { case 0: case 1: /* Touch only */ state = packet[0] & BIT(7); hover = packet[0] & BIT(5); palm = packet[0] & BIT(4); id = (packet[0] & 0x0F) - 1; x = ((packet[1] & 0x0F) << 8) | packet[2]; y = (((packet[1] >> 4) & 0x0F) << 8) | packet[3]; pressure = packet[4]; size = packet[5]; if (ts->event_format == 0) { touch_major = packet[5]; touch_minor = packet[5]; } else { touch_major = packet[6]; touch_minor = packet[7]; } break; case 3: default: /* Touch + Force(Pressure) */ id = (packet[0] & 0x0F) - 1; hover = packet[1] & BIT(2); palm = packet[1] & BIT(1); state = packet[1] & BIT(0); x = ((packet[2] & 0x0F) << 8) | packet[3]; y = (((packet[2] >> 4) & 0x0F) << 8) | packet[4]; size = packet[6]; pressure_stage = (packet[7] & 0xF0) >> 4; pressure = ((packet[7] & 0x0F) << 8) | packet[8]; touch_major = packet[9]; touch_minor = packet[10]; break; } dev_dbg(&ts->client->dev, "Screen - Slot: %d State: %d X: %04d Y: %04d Z: %d\n", id, state, x, y, pressure); if (unlikely(id < 0 || id >= MIP4_MAX_FINGERS)) { dev_err(&ts->client->dev, "Screen - invalid slot ID: %d\n", id); } else if (state) { /* Press or Move event */ input_mt_slot(ts->input, id); input_mt_report_slot_state(ts->input, MT_TOOL_FINGER, true); input_report_abs(ts->input, ABS_MT_POSITION_X, x); input_report_abs(ts->input, ABS_MT_POSITION_Y, y); input_report_abs(ts->input, ABS_MT_PRESSURE, pressure); input_report_abs(ts->input, ABS_MT_TOUCH_MAJOR, touch_major); input_report_abs(ts->input, ABS_MT_TOUCH_MINOR, touch_minor); } else { /* Release event */ input_mt_slot(ts->input, id); input_mt_report_slot_state(ts->input, MT_TOOL_FINGER, 0); } input_mt_sync_frame(ts->input); }

Contributors

PersonTokensPropCommitsCommitProp
Sangwon Jee531100.00%1100.00%
Total531100.00%1100.00%


static int mip4_handle_packet(struct mip4_ts *ts, u8 *packet) { u8 type; switch (ts->event_format) { case 0: case 1: type = (packet[0] & 0x40) >> 6; break; case 3: type = (packet[0] & 0xF0) >> 4; break; default: /* Should not happen unless we have corrupted firmware */ return -EINVAL; } dev_dbg(&ts->client->dev, "Type: %d\n", type); /* Report input event */ switch (type) { case MIP4_EVENT_INPUT_TYPE_KEY: mip4_report_keys(ts, packet); break; case MIP4_EVENT_INPUT_TYPE_SCREEN: mip4_report_touch(ts, packet); break; default: dev_err(&ts->client->dev, "Unknown event type: %d\n", type); break; } return 0; }

Contributors

PersonTokensPropCommitsCommitProp
Sangwon Jee131100.00%1100.00%
Total131100.00%1100.00%


static irqreturn_t mip4_interrupt(int irq, void *dev_id) { struct mip4_ts *ts = dev_id; struct i2c_client *client = ts->client; unsigned int i; int error; u8 cmd[2]; u8 size; bool alert; /* Read packet info */ cmd[0] = MIP4_R0_EVENT; cmd[1] = MIP4_R1_EVENT_PACKET_INFO; error = mip4_i2c_xfer(ts, cmd, sizeof(cmd), ts->buf, 1); if (error) { dev_err(&client->dev, "Failed to read packet info: %d\n", error); goto out; } size = ts->buf[0] & 0x7F; alert = ts->buf[0] & BIT(7); dev_dbg(&client->dev, "packet size: %d, alert: %d\n", size, alert); /* Check size */ if (!size) { dev_err(&client->dev, "Empty packet\n"); goto out; } /* Read packet data */ cmd[0] = MIP4_R0_EVENT; cmd[1] = MIP4_R1_EVENT_PACKET_DATA; error = mip4_i2c_xfer(ts, cmd, sizeof(cmd), ts->buf, size); if (error) { dev_err(&client->dev, "Failed to read packet data: %d\n", error); goto out; } if (alert) { dev_dbg(&client->dev, "Alert: %d\n", ts->buf[0]); } else { for (i = 0; i < size; i += ts->event_size) { error = mip4_handle_packet(ts, &ts->buf[i]); if (error) break; } input_sync(ts->input); } out: return IRQ_HANDLED; }

Contributors

PersonTokensPropCommitsCommitProp
Sangwon Jee296100.00%1100.00%
Total296100.00%1100.00%


static int mip4_input_open(struct input_dev *dev) { struct mip4_ts *ts = input_get_drvdata(dev); return mip4_enable(ts); }

Contributors

PersonTokensPropCommitsCommitProp
Sangwon Jee27100.00%1100.00%
Total27100.00%1100.00%


static void mip4_input_close(struct input_dev *dev) { struct mip4_ts *ts = input_get_drvdata(dev); mip4_disable(ts); }

Contributors

PersonTokensPropCommitsCommitProp
Sangwon Jee26100.00%1100.00%
Total26100.00%1100.00%

/***************************************************************** * Firmware update *****************************************************************/ /* Firmware Info */ #define MIP4_BL_PAGE_SIZE 512 /* 512 */ #define MIP4_BL_PACKET_SIZE 512 /* 512, 256, 128, 64, ... */ /* * Firmware binary tail info */