10f382cadSJob Noorman // SPDX-License-Identifier: GPL-2.0-only 20f382cadSJob Noorman /* 30f382cadSJob Noorman * Driver for Himax hx83112b touchscreens 40f382cadSJob Noorman * 50f382cadSJob Noorman * Copyright (C) 2022 Job Noorman <job@noorman.info> 60f382cadSJob Noorman * 70f382cadSJob Noorman * This code is based on "Himax Android Driver Sample Code for QCT platform": 80f382cadSJob Noorman * 90f382cadSJob Noorman * Copyright (C) 2017 Himax Corporation. 100f382cadSJob Noorman */ 110f382cadSJob Noorman 120f382cadSJob Noorman #include <linux/delay.h> 130f382cadSJob Noorman #include <linux/err.h> 140f382cadSJob Noorman #include <linux/gpio/consumer.h> 150f382cadSJob Noorman #include <linux/i2c.h> 160f382cadSJob Noorman #include <linux/input.h> 170f382cadSJob Noorman #include <linux/input/mt.h> 180f382cadSJob Noorman #include <linux/input/touchscreen.h> 190f382cadSJob Noorman #include <linux/interrupt.h> 200f382cadSJob Noorman #include <linux/kernel.h> 210f382cadSJob Noorman #include <linux/regmap.h> 220f382cadSJob Noorman 230f382cadSJob Noorman #define HIMAX_ID_83112B 0x83112b 240f382cadSJob Noorman 250f382cadSJob Noorman #define HIMAX_MAX_POINTS 10 260f382cadSJob Noorman 270f382cadSJob Noorman #define HIMAX_REG_CFG_SET_ADDR 0x00 280f382cadSJob Noorman #define HIMAX_REG_CFG_INIT_READ 0x0c 290f382cadSJob Noorman #define HIMAX_REG_CFG_READ_VALUE 0x08 300f382cadSJob Noorman #define HIMAX_REG_READ_EVENT 0x30 310f382cadSJob Noorman 320f382cadSJob Noorman #define HIMAX_CFG_PRODUCT_ID 0x900000d0 330f382cadSJob Noorman 340f382cadSJob Noorman #define HIMAX_INVALID_COORD 0xffff 350f382cadSJob Noorman 360f382cadSJob Noorman struct himax_event_point { 370f382cadSJob Noorman __be16 x; 380f382cadSJob Noorman __be16 y; 390f382cadSJob Noorman } __packed; 400f382cadSJob Noorman 410f382cadSJob Noorman struct himax_event { 420f382cadSJob Noorman struct himax_event_point points[HIMAX_MAX_POINTS]; 430f382cadSJob Noorman u8 majors[HIMAX_MAX_POINTS]; 440f382cadSJob Noorman u8 pad0[2]; 450f382cadSJob Noorman u8 num_points; 460f382cadSJob Noorman u8 pad1[2]; 470f382cadSJob Noorman u8 checksum_fix; 480f382cadSJob Noorman } __packed; 490f382cadSJob Noorman 500f382cadSJob Noorman static_assert(sizeof(struct himax_event) == 56); 510f382cadSJob Noorman 520f382cadSJob Noorman struct himax_ts_data { 530f382cadSJob Noorman struct gpio_desc *gpiod_rst; 540f382cadSJob Noorman struct input_dev *input_dev; 550f382cadSJob Noorman struct i2c_client *client; 560f382cadSJob Noorman struct regmap *regmap; 570f382cadSJob Noorman struct touchscreen_properties props; 580f382cadSJob Noorman }; 590f382cadSJob Noorman 600f382cadSJob Noorman static const struct regmap_config himax_regmap_config = { 610f382cadSJob Noorman .reg_bits = 8, 620f382cadSJob Noorman .val_bits = 32, 630f382cadSJob Noorman .val_format_endian = REGMAP_ENDIAN_LITTLE, 640f382cadSJob Noorman }; 650f382cadSJob Noorman 660f382cadSJob Noorman static int himax_read_config(struct himax_ts_data *ts, u32 address, u32 *dst) 670f382cadSJob Noorman { 680f382cadSJob Noorman int error; 690f382cadSJob Noorman 700f382cadSJob Noorman error = regmap_write(ts->regmap, HIMAX_REG_CFG_SET_ADDR, address); 710f382cadSJob Noorman if (error) 720f382cadSJob Noorman return error; 730f382cadSJob Noorman 740f382cadSJob Noorman error = regmap_write(ts->regmap, HIMAX_REG_CFG_INIT_READ, 0x0); 750f382cadSJob Noorman if (error) 760f382cadSJob Noorman return error; 770f382cadSJob Noorman 780f382cadSJob Noorman error = regmap_read(ts->regmap, HIMAX_REG_CFG_READ_VALUE, dst); 790f382cadSJob Noorman if (error) 800f382cadSJob Noorman return error; 810f382cadSJob Noorman 820f382cadSJob Noorman return 0; 830f382cadSJob Noorman } 840f382cadSJob Noorman 850f382cadSJob Noorman static void himax_reset(struct himax_ts_data *ts) 860f382cadSJob Noorman { 870f382cadSJob Noorman gpiod_set_value_cansleep(ts->gpiod_rst, 1); 880f382cadSJob Noorman 890f382cadSJob Noorman /* Delay copied from downstream driver */ 900f382cadSJob Noorman msleep(20); 910f382cadSJob Noorman gpiod_set_value_cansleep(ts->gpiod_rst, 0); 920f382cadSJob Noorman 930f382cadSJob Noorman /* 940f382cadSJob Noorman * The downstream driver doesn't contain this delay but is seems safer 950f382cadSJob Noorman * to include it. The range is just a guess that seems to work well. 960f382cadSJob Noorman */ 970f382cadSJob Noorman usleep_range(1000, 1100); 980f382cadSJob Noorman } 990f382cadSJob Noorman 1000f382cadSJob Noorman static int himax_read_product_id(struct himax_ts_data *ts, u32 *product_id) 1010f382cadSJob Noorman { 1020f382cadSJob Noorman int error; 1030f382cadSJob Noorman 1040f382cadSJob Noorman error = himax_read_config(ts, HIMAX_CFG_PRODUCT_ID, product_id); 1050f382cadSJob Noorman if (error) 1060f382cadSJob Noorman return error; 1070f382cadSJob Noorman 1080f382cadSJob Noorman *product_id >>= 8; 1090f382cadSJob Noorman return 0; 1100f382cadSJob Noorman } 1110f382cadSJob Noorman 1120f382cadSJob Noorman static int himax_check_product_id(struct himax_ts_data *ts) 1130f382cadSJob Noorman { 1140f382cadSJob Noorman int error; 1150f382cadSJob Noorman u32 product_id; 1160f382cadSJob Noorman 1170f382cadSJob Noorman error = himax_read_product_id(ts, &product_id); 1180f382cadSJob Noorman if (error) 1190f382cadSJob Noorman return error; 1200f382cadSJob Noorman 1210f382cadSJob Noorman dev_dbg(&ts->client->dev, "Product id: %x\n", product_id); 1220f382cadSJob Noorman 1230f382cadSJob Noorman switch (product_id) { 1240f382cadSJob Noorman case HIMAX_ID_83112B: 1250f382cadSJob Noorman return 0; 1260f382cadSJob Noorman 1270f382cadSJob Noorman default: 1280f382cadSJob Noorman dev_err(&ts->client->dev, 1290f382cadSJob Noorman "Unknown product id: %x\n", product_id); 1300f382cadSJob Noorman return -EINVAL; 1310f382cadSJob Noorman } 1320f382cadSJob Noorman } 1330f382cadSJob Noorman 1340f382cadSJob Noorman static int himax_input_register(struct himax_ts_data *ts) 1350f382cadSJob Noorman { 1360f382cadSJob Noorman int error; 1370f382cadSJob Noorman 1380f382cadSJob Noorman ts->input_dev = devm_input_allocate_device(&ts->client->dev); 1390f382cadSJob Noorman if (!ts->input_dev) { 1400f382cadSJob Noorman dev_err(&ts->client->dev, "Failed to allocate input device\n"); 1410f382cadSJob Noorman return -ENOMEM; 1420f382cadSJob Noorman } 1430f382cadSJob Noorman 1440f382cadSJob Noorman ts->input_dev->name = "Himax Touchscreen"; 1450f382cadSJob Noorman 1460f382cadSJob Noorman input_set_capability(ts->input_dev, EV_ABS, ABS_MT_POSITION_X); 1470f382cadSJob Noorman input_set_capability(ts->input_dev, EV_ABS, ABS_MT_POSITION_Y); 1480f382cadSJob Noorman input_set_abs_params(ts->input_dev, ABS_MT_WIDTH_MAJOR, 0, 200, 0, 0); 1490f382cadSJob Noorman input_set_abs_params(ts->input_dev, ABS_MT_TOUCH_MAJOR, 0, 200, 0, 0); 1500f382cadSJob Noorman 1510f382cadSJob Noorman touchscreen_parse_properties(ts->input_dev, true, &ts->props); 1520f382cadSJob Noorman 1530f382cadSJob Noorman error = input_mt_init_slots(ts->input_dev, HIMAX_MAX_POINTS, 1540f382cadSJob Noorman INPUT_MT_DIRECT | INPUT_MT_DROP_UNUSED); 1550f382cadSJob Noorman if (error) { 1560f382cadSJob Noorman dev_err(&ts->client->dev, 1570f382cadSJob Noorman "Failed to initialize MT slots: %d\n", error); 1580f382cadSJob Noorman return error; 1590f382cadSJob Noorman } 1600f382cadSJob Noorman 1610f382cadSJob Noorman error = input_register_device(ts->input_dev); 1620f382cadSJob Noorman if (error) { 1630f382cadSJob Noorman dev_err(&ts->client->dev, 1640f382cadSJob Noorman "Failed to register input device: %d\n", error); 1650f382cadSJob Noorman return error; 1660f382cadSJob Noorman } 1670f382cadSJob Noorman 1680f382cadSJob Noorman return 0; 1690f382cadSJob Noorman } 1700f382cadSJob Noorman 1710f382cadSJob Noorman static u8 himax_event_get_num_points(const struct himax_event *event) 1720f382cadSJob Noorman { 1730f382cadSJob Noorman if (event->num_points == 0xff) 1740f382cadSJob Noorman return 0; 1750f382cadSJob Noorman else 1760f382cadSJob Noorman return event->num_points & 0x0f; 1770f382cadSJob Noorman } 1780f382cadSJob Noorman 1790f382cadSJob Noorman static bool himax_process_event_point(struct himax_ts_data *ts, 1800f382cadSJob Noorman const struct himax_event *event, 1810f382cadSJob Noorman int point_index) 1820f382cadSJob Noorman { 1830f382cadSJob Noorman const struct himax_event_point *point = &event->points[point_index]; 1840f382cadSJob Noorman u16 x = be16_to_cpu(point->x); 1850f382cadSJob Noorman u16 y = be16_to_cpu(point->y); 1860f382cadSJob Noorman u8 w = event->majors[point_index]; 1870f382cadSJob Noorman 1880f382cadSJob Noorman if (x == HIMAX_INVALID_COORD || y == HIMAX_INVALID_COORD) 1890f382cadSJob Noorman return false; 1900f382cadSJob Noorman 1910f382cadSJob Noorman input_mt_slot(ts->input_dev, point_index); 1920f382cadSJob Noorman input_mt_report_slot_state(ts->input_dev, MT_TOOL_FINGER, true); 1930f382cadSJob Noorman touchscreen_report_pos(ts->input_dev, &ts->props, x, y, true); 1940f382cadSJob Noorman input_report_abs(ts->input_dev, ABS_MT_TOUCH_MAJOR, w); 1950f382cadSJob Noorman input_report_abs(ts->input_dev, ABS_MT_WIDTH_MAJOR, w); 1960f382cadSJob Noorman return true; 1970f382cadSJob Noorman } 1980f382cadSJob Noorman 1990f382cadSJob Noorman static void himax_process_event(struct himax_ts_data *ts, 2000f382cadSJob Noorman const struct himax_event *event) 2010f382cadSJob Noorman { 2020f382cadSJob Noorman int i; 2030f382cadSJob Noorman int num_points_left = himax_event_get_num_points(event); 2040f382cadSJob Noorman 2050f382cadSJob Noorman for (i = 0; i < HIMAX_MAX_POINTS && num_points_left > 0; i++) { 2060f382cadSJob Noorman if (himax_process_event_point(ts, event, i)) 2070f382cadSJob Noorman num_points_left--; 2080f382cadSJob Noorman } 2090f382cadSJob Noorman 2100f382cadSJob Noorman input_mt_sync_frame(ts->input_dev); 2110f382cadSJob Noorman input_sync(ts->input_dev); 2120f382cadSJob Noorman } 2130f382cadSJob Noorman 2140f382cadSJob Noorman static bool himax_verify_checksum(struct himax_ts_data *ts, 2150f382cadSJob Noorman const struct himax_event *event) 2160f382cadSJob Noorman { 2170f382cadSJob Noorman u8 *data = (u8 *)event; 2180f382cadSJob Noorman int i; 2190f382cadSJob Noorman u16 checksum = 0; 2200f382cadSJob Noorman 2210f382cadSJob Noorman for (i = 0; i < sizeof(*event); i++) 2220f382cadSJob Noorman checksum += data[i]; 2230f382cadSJob Noorman 2240f382cadSJob Noorman if ((checksum & 0x00ff) != 0) { 2250f382cadSJob Noorman dev_err(&ts->client->dev, "Wrong event checksum: %04x\n", 2260f382cadSJob Noorman checksum); 2270f382cadSJob Noorman return false; 2280f382cadSJob Noorman } 2290f382cadSJob Noorman 2300f382cadSJob Noorman return true; 2310f382cadSJob Noorman } 2320f382cadSJob Noorman 2330f382cadSJob Noorman static int himax_handle_input(struct himax_ts_data *ts) 2340f382cadSJob Noorman { 2350f382cadSJob Noorman int error; 2360f382cadSJob Noorman struct himax_event event; 2370f382cadSJob Noorman 2380f382cadSJob Noorman error = regmap_raw_read(ts->regmap, HIMAX_REG_READ_EVENT, &event, 2390f382cadSJob Noorman sizeof(event)); 2400f382cadSJob Noorman if (error) { 2410f382cadSJob Noorman dev_err(&ts->client->dev, "Failed to read input event: %d\n", 2420f382cadSJob Noorman error); 2430f382cadSJob Noorman return error; 2440f382cadSJob Noorman } 2450f382cadSJob Noorman 2460f382cadSJob Noorman /* 2470f382cadSJob Noorman * Only process the current event when it has a valid checksum but 2480f382cadSJob Noorman * don't consider it a fatal error when it doesn't. 2490f382cadSJob Noorman */ 2500f382cadSJob Noorman if (himax_verify_checksum(ts, &event)) 2510f382cadSJob Noorman himax_process_event(ts, &event); 2520f382cadSJob Noorman 2530f382cadSJob Noorman return 0; 2540f382cadSJob Noorman } 2550f382cadSJob Noorman 2560f382cadSJob Noorman static irqreturn_t himax_irq_handler(int irq, void *dev_id) 2570f382cadSJob Noorman { 2580f382cadSJob Noorman int error; 2590f382cadSJob Noorman struct himax_ts_data *ts = dev_id; 2600f382cadSJob Noorman 2610f382cadSJob Noorman error = himax_handle_input(ts); 2620f382cadSJob Noorman if (error) 2630f382cadSJob Noorman return IRQ_NONE; 2640f382cadSJob Noorman 2650f382cadSJob Noorman return IRQ_HANDLED; 2660f382cadSJob Noorman } 2670f382cadSJob Noorman 2684d1c7cc6SUwe Kleine-König static int himax_probe(struct i2c_client *client) 2690f382cadSJob Noorman { 2700f382cadSJob Noorman int error; 2710f382cadSJob Noorman struct device *dev = &client->dev; 2720f382cadSJob Noorman struct himax_ts_data *ts; 2730f382cadSJob Noorman 2740f382cadSJob Noorman if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) { 2750f382cadSJob Noorman dev_err(dev, "I2C check functionality failed\n"); 2760f382cadSJob Noorman return -ENXIO; 2770f382cadSJob Noorman } 2780f382cadSJob Noorman 2790f382cadSJob Noorman ts = devm_kzalloc(dev, sizeof(*ts), GFP_KERNEL); 2800f382cadSJob Noorman if (!ts) 2810f382cadSJob Noorman return -ENOMEM; 2820f382cadSJob Noorman 2830f382cadSJob Noorman i2c_set_clientdata(client, ts); 2840f382cadSJob Noorman ts->client = client; 2850f382cadSJob Noorman 2860f382cadSJob Noorman ts->regmap = devm_regmap_init_i2c(client, &himax_regmap_config); 2870f382cadSJob Noorman error = PTR_ERR_OR_ZERO(ts->regmap); 2880f382cadSJob Noorman if (error) { 2890f382cadSJob Noorman dev_err(dev, "Failed to initialize regmap: %d\n", error); 2900f382cadSJob Noorman return error; 2910f382cadSJob Noorman } 2920f382cadSJob Noorman 2930f382cadSJob Noorman ts->gpiod_rst = devm_gpiod_get(dev, "reset", GPIOD_OUT_HIGH); 2940f382cadSJob Noorman error = PTR_ERR_OR_ZERO(ts->gpiod_rst); 2950f382cadSJob Noorman if (error) { 2960f382cadSJob Noorman dev_err(dev, "Failed to get reset GPIO: %d\n", error); 2970f382cadSJob Noorman return error; 2980f382cadSJob Noorman } 2990f382cadSJob Noorman 3000f382cadSJob Noorman himax_reset(ts); 3010f382cadSJob Noorman 3020f382cadSJob Noorman error = himax_check_product_id(ts); 3030f382cadSJob Noorman if (error) 3040f382cadSJob Noorman return error; 3050f382cadSJob Noorman 3060f382cadSJob Noorman error = himax_input_register(ts); 3070f382cadSJob Noorman if (error) 3080f382cadSJob Noorman return error; 3090f382cadSJob Noorman 3100f382cadSJob Noorman error = devm_request_threaded_irq(dev, client->irq, NULL, 3110f382cadSJob Noorman himax_irq_handler, IRQF_ONESHOT, 3120f382cadSJob Noorman client->name, ts); 3130f382cadSJob Noorman if (error) 3140f382cadSJob Noorman return error; 3150f382cadSJob Noorman 3160f382cadSJob Noorman return 0; 3170f382cadSJob Noorman } 3180f382cadSJob Noorman 3190f382cadSJob Noorman static int himax_suspend(struct device *dev) 3200f382cadSJob Noorman { 3210f382cadSJob Noorman struct himax_ts_data *ts = dev_get_drvdata(dev); 3220f382cadSJob Noorman 3230f382cadSJob Noorman disable_irq(ts->client->irq); 3240f382cadSJob Noorman return 0; 3250f382cadSJob Noorman } 3260f382cadSJob Noorman 3270f382cadSJob Noorman static int himax_resume(struct device *dev) 3280f382cadSJob Noorman { 3290f382cadSJob Noorman struct himax_ts_data *ts = dev_get_drvdata(dev); 3300f382cadSJob Noorman 3310f382cadSJob Noorman enable_irq(ts->client->irq); 3320f382cadSJob Noorman return 0; 3330f382cadSJob Noorman } 3340f382cadSJob Noorman 3350f382cadSJob Noorman static DEFINE_SIMPLE_DEV_PM_OPS(himax_pm_ops, himax_suspend, himax_resume); 3360f382cadSJob Noorman 3370f382cadSJob Noorman static const struct i2c_device_id himax_ts_id[] = { 3380f382cadSJob Noorman { "hx83112b", 0 }, 3390f382cadSJob Noorman { /* sentinel */ } 3400f382cadSJob Noorman }; 3410f382cadSJob Noorman MODULE_DEVICE_TABLE(i2c, himax_ts_id); 3420f382cadSJob Noorman 3430f382cadSJob Noorman #ifdef CONFIG_OF 3440f382cadSJob Noorman static const struct of_device_id himax_of_match[] = { 3450f382cadSJob Noorman { .compatible = "himax,hx83112b" }, 3460f382cadSJob Noorman { /* sentinel */ } 3470f382cadSJob Noorman }; 3480f382cadSJob Noorman MODULE_DEVICE_TABLE(of, himax_of_match); 3490f382cadSJob Noorman #endif 3500f382cadSJob Noorman 3510f382cadSJob Noorman static struct i2c_driver himax_ts_driver = { 352*d8bde56dSUwe Kleine-König .probe = himax_probe, 3530f382cadSJob Noorman .id_table = himax_ts_id, 3540f382cadSJob Noorman .driver = { 3550f382cadSJob Noorman .name = "Himax-hx83112b-TS", 3560f382cadSJob Noorman .of_match_table = of_match_ptr(himax_of_match), 3570f382cadSJob Noorman .pm = pm_sleep_ptr(&himax_pm_ops), 3580f382cadSJob Noorman }, 3590f382cadSJob Noorman }; 3600f382cadSJob Noorman module_i2c_driver(himax_ts_driver); 3610f382cadSJob Noorman 3620f382cadSJob Noorman MODULE_AUTHOR("Job Noorman <job@noorman.info>"); 3630f382cadSJob Noorman MODULE_DESCRIPTION("Himax hx83112b touchscreen driver"); 3640f382cadSJob Noorman MODULE_LICENSE("GPL"); 365